summaryrefslogtreecommitdiff
path: root/internal/storage/templates.go
blob: 57abaa4e8ba19d7df8d26a3c2f540ddc2579752a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package storage

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/thepeterstone/claudomator/internal/task"
)

// ErrTemplateNotFound is returned when a template ID does not exist.
var ErrTemplateNotFound = errors.New("template not found")

// Template is a reusable task configuration saved for repeated use.
type Template struct {
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Agent      task.AgentConfig `json:"agent"`
	Timeout     string            `json:"timeout"`
	Priority    string            `json:"priority"`
	Tags        []string          `json:"tags"`
	CreatedAt   time.Time         `json:"created_at"`
	UpdatedAt   time.Time         `json:"updated_at"`
}

// CreateTemplate inserts a new template.
func (s *DB) CreateTemplate(tmpl *Template) error {
	configJSON, err := json.Marshal(tmpl.Agent)
	if err != nil {
		return fmt.Errorf("marshaling config: %w", err)
	}
	tagsJSON, err := json.Marshal(tmpl.Tags)
	if err != nil {
		return fmt.Errorf("marshaling tags: %w", err)
	}
	_, err = s.db.Exec(`
		INSERT INTO templates (id, name, description, config_json, timeout, priority, tags_json, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		tmpl.ID, tmpl.Name, tmpl.Description, string(configJSON),
		tmpl.Timeout, tmpl.Priority, string(tagsJSON),
		tmpl.CreatedAt.UTC(), tmpl.UpdatedAt.UTC(),
	)
	return err
}

// GetTemplate retrieves a template by ID, returning ErrTemplateNotFound if missing.
func (s *DB) GetTemplate(id string) (*Template, error) {
	row := s.db.QueryRow(`SELECT id, name, description, config_json, timeout, priority, tags_json, created_at, updated_at FROM templates WHERE id = ?`, id)
	return scanTemplate(row)
}

// ListTemplates returns all templates ordered by name.
func (s *DB) ListTemplates() ([]*Template, error) {
	rows, err := s.db.Query(`SELECT id, name, description, config_json, timeout, priority, tags_json, created_at, updated_at FROM templates ORDER BY name ASC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var templates []*Template
	for rows.Next() {
		tmpl, err := scanTemplate(rows)
		if err != nil {
			return nil, err
		}
		templates = append(templates, tmpl)
	}
	return templates, rows.Err()
}

// UpdateTemplate fully replaces a template's fields. Returns ErrTemplateNotFound if the ID is missing.
func (s *DB) UpdateTemplate(tmpl *Template) error {
	configJSON, err := json.Marshal(tmpl.Agent)
	if err != nil {
		return fmt.Errorf("marshaling config: %w", err)
	}
	tagsJSON, err := json.Marshal(tmpl.Tags)
	if err != nil {
		return fmt.Errorf("marshaling tags: %w", err)
	}
	result, err := s.db.Exec(`
		UPDATE templates SET name = ?, description = ?, config_json = ?, timeout = ?, priority = ?, tags_json = ?, updated_at = ?
		WHERE id = ?`,
		tmpl.Name, tmpl.Description, string(configJSON), tmpl.Timeout, tmpl.Priority, string(tagsJSON),
		tmpl.UpdatedAt.UTC(), tmpl.ID,
	)
	if err != nil {
		return err
	}
	n, err := result.RowsAffected()
	if err != nil {
		return err
	}
	if n == 0 {
		return ErrTemplateNotFound
	}
	return nil
}

// DeleteTemplate removes a template by ID. Returns ErrTemplateNotFound if the ID is missing.
func (s *DB) DeleteTemplate(id string) error {
	result, err := s.db.Exec(`DELETE FROM templates WHERE id = ?`, id)
	if err != nil {
		return err
	}
	n, err := result.RowsAffected()
	if err != nil {
		return err
	}
	if n == 0 {
		return ErrTemplateNotFound
	}
	return nil
}

func scanTemplate(row scanner) (*Template, error) {
	var (
		tmpl       Template
		configJSON string
		tagsJSON   string
	)
	err := row.Scan(&tmpl.ID, &tmpl.Name, &tmpl.Description, &configJSON,
		&tmpl.Timeout, &tmpl.Priority, &tagsJSON, &tmpl.CreatedAt, &tmpl.UpdatedAt)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrTemplateNotFound
		}
		return nil, err
	}
	if err := json.Unmarshal([]byte(configJSON), &tmpl.Agent); err != nil {
		return nil, fmt.Errorf("unmarshaling config: %w", err)
	}
	if err := json.Unmarshal([]byte(tagsJSON), &tmpl.Tags); err != nil {
		return nil, fmt.Errorf("unmarshaling tags: %w", err)
	}
	return &tmpl, nil
}