package sqlite import ( "context" "database/sql" "fmt" "slices" "strings" "inbox/internal/domain/skill" ) func (s *Store) UpsertSkill(ctx context.Context, value skill.Definition, changedBy string) (skill.Definition, error) { if err := value.Validate(); err != nil { return skill.Definition{}, err } return upsertVersionedConfig(ctx, s, value, changedBy, versionedConfigUpsertSpec[skill.Definition]{ entityName: "skill", idKind: "skill", loadTx: func(ctx context.Context, tx *sql.Tx) (skill.Definition, error) { return getSkillByKeyTx(ctx, tx, value.SkillKey) }, current: func(item skill.Definition) versionedConfigCurrent { return versionedConfigCurrent{ ID: item.ID, Version: item.Version, CreatedAt: item.CreatedAt, } }, applyMetadata: func(item *skill.Definition, meta versionedConfigMetadata) { item.ID = meta.ID item.Version = meta.Version item.CreatedAt = meta.CreatedAt item.UpdatedAt = meta.UpdatedAt }, writeTx: func(ctx context.Context, tx *sql.Tx, item skill.Definition) error { _, err := tx.ExecContext(ctx, ` INSERT INTO skills(id, skill_key, name, description, source_type, content_markdown, status, version, created_at, updated_at) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description, source_type = excluded.source_type, content_markdown = excluded.content_markdown, status = excluded.status, version = excluded.version, updated_at = excluded.updated_at `, item.ID, item.SkillKey, item.Name, item.Description, item.SourceType, item.ContentMarkdown, item.Status, item.Version, item.CreatedAt, item.UpdatedAt, ) return err }, }) } func (s *Store) ListSkillsByIDs(ctx context.Context, ids []string) (map[string]skill.Definition, error) { ids = slices.Clone(ids) if len(ids) == 0 { return map[string]skill.Definition{}, nil } placeholders := make([]string, len(ids)) args := make([]any, len(ids)) for i, id := range ids { placeholders[i] = "?" args[i] = id } query := fmt.Sprintf(` SELECT id, skill_key, name, description, source_type, content_markdown, status, version, created_at, updated_at FROM skills WHERE id IN (%s) `, strings.Join(placeholders, ", ")) rows, err := s.db.QueryContext(ctx, query, args...) if err != nil { return nil, fmt.Errorf("list skills by ids: %w", err) } defer rows.Close() out := make(map[string]skill.Definition, len(ids)) for rows.Next() { item, err := scanSkill(rows) if err != nil { return nil, err } out[item.ID] = item } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate skills: %w", err) } return out, nil } func (s *Store) GetSkillByKey(ctx context.Context, skillKey string) (skill.Definition, error) { row := s.db.QueryRowContext(ctx, ` SELECT id, skill_key, name, description, source_type, content_markdown, status, version, created_at, updated_at FROM skills WHERE skill_key = ? `, skillKey) return scanSkill(row) } func (s *Store) ListSkills(ctx context.Context) ([]skill.Definition, error) { rows, err := s.db.QueryContext(ctx, ` SELECT id, skill_key, name, description, source_type, content_markdown, status, version, created_at, updated_at FROM skills ORDER BY name, skill_key `) if err != nil { return nil, fmt.Errorf("list skills: %w", err) } defer rows.Close() var out []skill.Definition for rows.Next() { item, err := scanSkill(rows) if err != nil { return nil, err } out = append(out, item) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate skills: %w", err) } return out, nil } func (s *Store) DeleteSkillByKey(ctx context.Context, skillKey string) error { result, err := s.db.ExecContext(ctx, `DELETE FROM skills WHERE skill_key = ?`, skillKey) if err != nil { return fmt.Errorf("delete skill: %w", err) } return ensureAffected(result, sql.ErrNoRows) } func getSkillByKeyTx(ctx context.Context, tx *sql.Tx, skillKey string) (skill.Definition, error) { row := tx.QueryRowContext(ctx, ` SELECT id, skill_key, name, description, source_type, content_markdown, status, version, created_at, updated_at FROM skills WHERE skill_key = ? `, skillKey) return scanSkill(row) } func scanSkill(s scanner) (skill.Definition, error) { var item skill.Definition if err := s.Scan(&item.ID, &item.SkillKey, &item.Name, &item.Description, &item.SourceType, &item.ContentMarkdown, &item.Status, &item.Version, &item.CreatedAt, &item.UpdatedAt); err != nil { return skill.Definition{}, err } return item, nil }