Add comments feature (bd-162)
- Add comments table to SQLite schema - Add Comment type to internal/types - Implement AddIssueComment and GetIssueComments in storage layer - Update JSONL export/import to include comments - Add comments to 'bd show' output - Create 'bd comments' CLI command structure - Fix UpdateIssueID to update comments table and defer FK checks - Add GetIssueComments/AddIssueComment to Storage interface Note: CLI command needs daemon RPC support (tracked in bd-163) Amp-Thread-ID: https://ampcode.com/threads/T-ece10dd1-cf64-48ff-9adb-dd304d0bcb25 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -1155,6 +1155,12 @@ func (s *SQLiteStorage) UpdateIssueID(ctx context.Context, oldID, newID string,
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Defer foreign key checks until end of transaction
|
||||
_, err = tx.ExecContext(ctx, `PRAGMA defer_foreign_keys = ON`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to defer foreign keys: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
UPDATE issues
|
||||
SET id = ?, title = ?, description = ?, design = ?, acceptance_criteria = ?, notes = ?, updated_at = ?
|
||||
@@ -1184,6 +1190,11 @@ func (s *SQLiteStorage) UpdateIssueID(ctx context.Context, oldID, newID string,
|
||||
return fmt.Errorf("failed to update labels: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `UPDATE comments SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update comments: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
UPDATE dirty_issues SET issue_id = ? WHERE issue_id = ?
|
||||
`, newID, oldID)
|
||||
@@ -1716,6 +1727,81 @@ func (s *SQLiteStorage) GetMetadata(ctx context.Context, key string) (string, er
|
||||
return value, err
|
||||
}
|
||||
|
||||
// AddIssueComment adds a comment to an issue
|
||||
func (s *SQLiteStorage) AddIssueComment(ctx context.Context, issueID, author, text string) (*types.Comment, error) {
|
||||
// Verify issue exists
|
||||
var exists bool
|
||||
err := s.db.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM issues WHERE id = ?)`, issueID).Scan(&exists)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check issue existence: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("issue %s not found", issueID)
|
||||
}
|
||||
|
||||
// Insert comment
|
||||
result, err := s.db.ExecContext(ctx, `
|
||||
INSERT INTO comments (issue_id, author, text, created_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`, issueID, author, text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to insert comment: %w", err)
|
||||
}
|
||||
|
||||
// Get the inserted comment ID
|
||||
commentID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get comment ID: %w", err)
|
||||
}
|
||||
|
||||
// Fetch the complete comment
|
||||
comment := &types.Comment{}
|
||||
err = s.db.QueryRowContext(ctx, `
|
||||
SELECT id, issue_id, author, text, created_at
|
||||
FROM comments WHERE id = ?
|
||||
`, commentID).Scan(&comment.ID, &comment.IssueID, &comment.Author, &comment.Text, &comment.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch comment: %w", err)
|
||||
}
|
||||
|
||||
// Mark issue as dirty for JSONL export
|
||||
if err := s.MarkIssueDirty(ctx, issueID); err != nil {
|
||||
return nil, fmt.Errorf("failed to mark issue dirty: %w", err)
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
// GetIssueComments retrieves all comments for an issue
|
||||
func (s *SQLiteStorage) GetIssueComments(ctx context.Context, issueID string) ([]*types.Comment, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT id, issue_id, author, text, created_at
|
||||
FROM comments
|
||||
WHERE issue_id = ?
|
||||
ORDER BY created_at ASC
|
||||
`, issueID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query comments: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var comments []*types.Comment
|
||||
for rows.Next() {
|
||||
comment := &types.Comment{}
|
||||
err := rows.Scan(&comment.ID, &comment.IssueID, &comment.Author, &comment.Text, &comment.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan comment: %w", err)
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating comments: %w", err)
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (s *SQLiteStorage) Close() error {
|
||||
return s.db.Close()
|
||||
|
||||
Reference in New Issue
Block a user