package store import ( "context" "database/sql" "fmt" "time" "github.com/google/uuid" "github.com/lib/pq" "amcs/internal/types" ) type LearningStore struct { db *sql.DB } func NewLearningStore(db *sql.DB) *LearningStore { return &LearningStore{db: db} } func (s *LearningStore) Create(ctx context.Context, l *types.Learning) error { query := ` INSERT INTO learnings ( summary, details, category, area, status, priority, confidence, action_required, source_type, source_ref, project_id, related_thought_id, related_skill_id, reviewed_by, reviewed_at, duplicate_of, supersedes, tags ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 ) RETURNING guid, created_at, updated_at` var id uuid.UUID var createdAt, updatedAt time.Time err := s.db.QueryRowContext(ctx, query, l.Summary, l.Details, l.Category, l.Area, l.Status, l.Priority, l.Confidence, l.ActionRequired, l.SourceType, l.SourceRef, l.ProjectID, l.RelatedThoughtID, l.RelatedSkillID, l.ReviewedBy, l.ReviewedAt, l.DuplicateOf, l.Supersedes, pq.Array(l.Tags), ).Scan(&id, &createdAt, &updatedAt) if err != nil { return fmt.Errorf("failed to create learning: %w", err) } l.ID = id l.CreatedAt = createdAt l.UpdatedAt = updatedAt return nil } func (s *LearningStore) Get(ctx context.Context, id uuid.UUID) (*types.Learning, error) { query := `SELECT * FROM learnings WHERE guid = $1` l := &types.Learning{} err := s.db.QueryRowContext(ctx, query, id).Scan( &l.ID, &l.Summary, &l.Details, &l.Category, &l.Area, &l.Status, &l.Priority, &l.Confidence, &l.ActionRequired, &l.SourceType, &l.SourceRef, &l.ProjectID, &l.RelatedThoughtID, &l.RelatedSkillID, &l.ReviewedBy, &l.ReviewedAt, &l.DuplicateOf, &l.Supersedes, &l.Tags, &l.CreatedAt, &l.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("failed to get learning: %w", err) } return l, nil } func (s *LearningStore) Update(ctx context.Context, l *types.Learning) error { query := ` UPDATE learnings SET summary=$1, details=$2, category=$3, area=$4, status=$5, priority=$6, confidence=$7, action_required=$8, source_type=$9, source_ref=$10, project_id=$11, related_thought_id=$12, related_skill_id=$13, reviewed_by=$14, reviewed_at=$15, duplicate_of=$16, supersedes=$17, tags=$18, updated_at=now() WHERE guid=$19` _, err := s.db.ExecContext(ctx, query, l.Summary, l.Details, l.Category, l.Area, l.Status, l.Priority, l.Confidence, l.ActionRequired, l.SourceType, l.SourceRef, l.ProjectID, l.RelatedThoughtID, l.RelatedSkillID, l.ReviewedBy, l.ReviewedAt, l.DuplicateOf, l.Supersedes, pq.Array(l.Tags), l.ID, ) if err != nil { return fmt.Errorf("failed to update learning: %w", err) } return nil } func (s *LearningStore) Delete(ctx context.Context, id uuid.UUID) error { query := `DELETE FROM learnings WHERE guid = $1` _, err := s.db.ExecContext(ctx, query, id) if err != nil { return fmt.Errorf("failed to delete learning: %w", err) } return nil } func (s *LearningStore) List(ctx context.Context, filter types.LearningFilter) ([]types.Learning, error) { query := `SELECT * FROM learnings WHERE 1=1` args := []interface{}{} argCount := 1 if filter.ProjectID != nil { query += fmt.Sprintf(` AND project_id = $%d`, argCount) args = append(args, *filter.ProjectID) argCount++ } if filter.Category != "" { query += fmt.Sprintf(` AND category = $%d`, argCount) args = append(args, filter.Category) argCount++ } if filter.Area != "" { query += fmt.Sprintf(` AND area = $%d`, argCount) args = append(args, filter.Area) argCount++ } if filter.Status != "" { query += fmt.Sprintf(` AND status = $%d`, argCount) args = append(args, filter.Status) argCount++ } if filter.Priority != "" { query += fmt.Sprintf(` AND priority = $%d`, argCount) args = append(args, filter.Priority) argCount++ } if filter.Tag != "" { query += fmt.Sprintf(` AND %d = ANY(tags)`, argCount) // Wait, tags is array. Correct is: query = fmt.Sprintf("%s AND $%d = ANY(tags)", query, argCount) args = append(args, filter.Tag) argCount++ } if filter.Query != "" { query += fmt.Sprintf(` AND to_tsvector('simple', summary || ' ' || coalesce(details, '')) @@ websearch_to_tsquery('simple', $%d)`, argCount) args = append(args, filter.Query) argCount++ } query += fmt.Sprintf(` ORDER BY created_at DESC LIMIT %d`, filter.Limit) if filter.Limit == 0 { query = query[:len(query)-10] // remove LIMIT 0 } rows, err := s.db.QueryContext(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() var learnings []types.Learning for rows.Next() { l := types.Learning{} err := rows.Scan( &l.ID, &l.Summary, &l.Details, &l.Category, &l.Area, &l.Status, &l.Priority, &l.Confidence, &l.ActionRequired, &l.SourceType, &l.SourceRef, &l.ProjectID, &l.RelatedThoughtID, &l.RelatedSkillID, &l.ReviewedBy, &l.ReviewedAt, &l.DuplicateOf, &l.Supersedes, &l.Tags, &l.CreatedAt, &l.UpdatedAt, ) if err != nil { return nil, err } learnings = append(learnings, l) } return learnings, nil }