Some checks failed
CI / build-and-test (push) Failing after -31m25s
* Update project creation and retrieval to use generated models * Modify skill addition and listing to utilize generated models * Refactor thought handling to incorporate generated models * Adjust tool annotations to align with new model structure * Update API calls in the UI to use new ResolveSpec-based endpoints * Enhance stats retrieval logic to aggregate thought metadata
209 lines
5.9 KiB
Go
209 lines
5.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/amcs/internal/generatedmodels"
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
|
|
)
|
|
|
|
func (db *DB) CreateLearning(ctx context.Context, learning thoughttypes.Learning) (thoughttypes.Learning, error) {
|
|
row := db.pool.QueryRow(ctx, `
|
|
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_learning_id,
|
|
supersedes_learning_id, tags
|
|
) values (
|
|
$1, $2, $3, $4, $5, $6, $7,
|
|
$8, $9, $10, $11, $12,
|
|
$13, $14, $15, $16,
|
|
$17, $18
|
|
)
|
|
returning id, created_at, updated_at
|
|
`,
|
|
strings.TrimSpace(learning.Summary),
|
|
strings.TrimSpace(learning.Details),
|
|
strings.TrimSpace(learning.Category),
|
|
strings.TrimSpace(learning.Area),
|
|
string(learning.Status),
|
|
string(learning.Priority),
|
|
string(learning.Confidence),
|
|
learning.ActionRequired,
|
|
nullableText(learning.SourceType),
|
|
nullableText(learning.SourceRef),
|
|
learning.ProjectID,
|
|
learning.RelatedThoughtID,
|
|
learning.RelatedSkillID,
|
|
nullableTextPtr(learning.ReviewedBy),
|
|
learning.ReviewedAt,
|
|
learning.DuplicateOfLearningID,
|
|
learning.SupersedesLearningID,
|
|
learning.Tags,
|
|
)
|
|
|
|
created := learning
|
|
var model generatedmodels.ModelPublicLearnings
|
|
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
|
|
return thoughttypes.Learning{}, fmt.Errorf("create learning: %w", err)
|
|
}
|
|
created.ID = model.ID.UUID()
|
|
created.CreatedAt = model.CreatedAt.Time()
|
|
created.UpdatedAt = model.UpdatedAt.Time()
|
|
return created, nil
|
|
}
|
|
|
|
func (db *DB) GetLearning(ctx context.Context, id uuid.UUID) (thoughttypes.Learning, error) {
|
|
row := db.pool.QueryRow(ctx, `
|
|
select id, 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_learning_id,
|
|
supersedes_learning_id, tags::text[], created_at, updated_at
|
|
from learnings
|
|
where id = $1
|
|
`, id)
|
|
|
|
learning, err := scanLearning(row)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return thoughttypes.Learning{}, fmt.Errorf("learning not found: %s", id)
|
|
}
|
|
return thoughttypes.Learning{}, fmt.Errorf("get learning: %w", err)
|
|
}
|
|
return learning, nil
|
|
}
|
|
|
|
func (db *DB) ListLearnings(ctx context.Context, filter thoughttypes.LearningFilter) ([]thoughttypes.Learning, error) {
|
|
args := make([]any, 0, 8)
|
|
conditions := make([]string, 0, 8)
|
|
|
|
if filter.ProjectID != nil {
|
|
args = append(args, *filter.ProjectID)
|
|
conditions = append(conditions, fmt.Sprintf("project_id = $%d", len(args)))
|
|
}
|
|
if value := strings.TrimSpace(filter.Category); value != "" {
|
|
args = append(args, value)
|
|
conditions = append(conditions, fmt.Sprintf("category = $%d", len(args)))
|
|
}
|
|
if value := strings.TrimSpace(filter.Area); value != "" {
|
|
args = append(args, value)
|
|
conditions = append(conditions, fmt.Sprintf("area = $%d", len(args)))
|
|
}
|
|
if value := strings.TrimSpace(filter.Status); value != "" {
|
|
args = append(args, value)
|
|
conditions = append(conditions, fmt.Sprintf("status = $%d", len(args)))
|
|
}
|
|
if value := strings.TrimSpace(filter.Priority); value != "" {
|
|
args = append(args, value)
|
|
conditions = append(conditions, fmt.Sprintf("priority = $%d", len(args)))
|
|
}
|
|
if value := strings.TrimSpace(filter.Tag); value != "" {
|
|
args = append(args, value)
|
|
conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args)))
|
|
}
|
|
if value := strings.TrimSpace(filter.Query); value != "" {
|
|
args = append(args, value)
|
|
conditions = append(conditions, fmt.Sprintf("to_tsvector('simple', summary || ' ' || coalesce(details, '')) @@ websearch_to_tsquery('simple', $%d)", len(args)))
|
|
}
|
|
|
|
query := `
|
|
select id, 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_learning_id,
|
|
supersedes_learning_id, tags::text[], created_at, updated_at
|
|
from learnings
|
|
`
|
|
if len(conditions) > 0 {
|
|
query += " where " + strings.Join(conditions, " and ")
|
|
}
|
|
query += " order by updated_at desc"
|
|
if filter.Limit > 0 {
|
|
args = append(args, filter.Limit)
|
|
query += fmt.Sprintf(" limit $%d", len(args))
|
|
}
|
|
|
|
rows, err := db.pool.Query(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list learnings: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]thoughttypes.Learning, 0)
|
|
for rows.Next() {
|
|
item, err := scanLearning(rows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scan learning: %w", err)
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate learnings: %w", err)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
type learningScanner interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func scanLearning(row learningScanner) (thoughttypes.Learning, error) {
|
|
var model generatedmodels.ModelPublicLearnings
|
|
var tags []string
|
|
|
|
err := row.Scan(
|
|
&model.ID,
|
|
&model.Summary,
|
|
&model.Details,
|
|
&model.Category,
|
|
&model.Area,
|
|
&model.Status,
|
|
&model.Priority,
|
|
&model.Confidence,
|
|
&model.ActionRequired,
|
|
&model.SourceType,
|
|
&model.SourceRef,
|
|
&model.ProjectID,
|
|
&model.RelatedThoughtID,
|
|
&model.RelatedSkillID,
|
|
&model.ReviewedBy,
|
|
&model.ReviewedAt,
|
|
&model.DuplicateOfLearningID,
|
|
&model.SupersedesLearningID,
|
|
&tags,
|
|
&model.CreatedAt,
|
|
&model.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return thoughttypes.Learning{}, err
|
|
}
|
|
|
|
if tags == nil {
|
|
tags = []string{}
|
|
}
|
|
return learningFromModel(model, tags), nil
|
|
}
|
|
|
|
func nullableText(value string) *string {
|
|
trimmed := strings.TrimSpace(value)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
return &trimmed
|
|
}
|
|
|
|
func nullableTextPtr(value *string) *string {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
trimmed := strings.TrimSpace(*value)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
return &trimmed
|
|
}
|