Files
ratatoskr-go/internal/storage/tasks.go

138 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package storage
import (
"context"
"database/sql"
"fmt"
)
// CreateTask создаёт задачу со статусом draft. Возвращает её ID.
// chat_id и task_tag передаются извне (в диалоге — chat.Address + uuid).
func (s *Storage) CreateTask(ctx context.Context, t *Task) (int64, error) {
now := Now()
res, err := s.db.ExecContext(ctx, `
INSERT INTO tasks (chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ChatID, t.Title, t.Goal, t.Repo, t.Why, t.AC,
t.TaskTag, StatusDraft, now, now,
)
if err != nil {
return 0, fmt.Errorf("%w: create task: %w", ErrDB, err)
}
id, err := res.LastInsertId()
if err != nil {
return 0, fmt.Errorf("%w: last insert id: %w", ErrDB, err)
}
t.ID = id
t.Status = StatusDraft
t.CreatedAt = now
t.UpdatedAt = now
return id, nil
}
// GetTask возвращает задачу по ID.
func (s *Storage) GetTask(ctx context.Context, id int64) (*Task, error) {
t := &Task{}
err := s.db.QueryRowContext(ctx, `
SELECT id, chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at
FROM tasks WHERE id = ?`, id).Scan(
&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo,
&t.Why, &t.AC, &t.TaskTag, &t.Status, &t.CreatedAt, &t.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("%w: task %d", ErrNotFound, id)
}
if err != nil {
return nil, fmt.Errorf("%w: get task %d: %w", ErrDB, id, err)
}
return t, nil
}
// UpdateTask обновляет поля задачи. Если newStatus указан и отличен от текущего —
// проверяет валидность перехода. Возвращает ошибку ErrInvalidStatus при недопустимом переходе.
func (s *Storage) UpdateTask(ctx context.Context, t *Task) error {
// получаем текущий статус для валидации перехода
var oldStatus Status
err := s.db.QueryRowContext(ctx, `SELECT status FROM tasks WHERE id = ?`, t.ID).Scan(&oldStatus)
if err == sql.ErrNoRows {
return fmt.Errorf("%w: task %d", ErrNotFound, t.ID)
}
if err != nil {
return fmt.Errorf("%w: get status %d: %w", ErrDB, t.ID, err)
}
if t.Status != "" && t.Status != oldStatus {
if !IsValidTransition(oldStatus, t.Status) {
return fmt.Errorf("%w: %s → %s for task %d", ErrInvalidStatus, oldStatus, t.Status, t.ID)
}
} else if t.Status == "" {
t.Status = oldStatus
}
now := Now()
res, err := s.db.ExecContext(ctx, `
UPDATE tasks
SET title=?, goal=?, repo=?, why=?, ac=?, status=?, updated_at=?
WHERE id=?`,
t.Title, t.Goal, t.Repo, t.Why, t.AC, t.Status, now, t.ID,
)
if err != nil {
return fmt.Errorf("%w: update task %d: %w", ErrDB, t.ID, err)
}
n, _ := res.RowsAffected()
if n == 0 {
return fmt.Errorf("%w: task %d", ErrNotFound, t.ID)
}
t.UpdatedAt = now
return nil
}
// ListTasks возвращает список задач с фильтрацией.
func (s *Storage) ListTasks(ctx context.Context, filter TaskFilter) ([]*Task, error) {
if filter.Limit <= 0 {
filter.Limit = 50
}
where := "1=1"
args := []any{}
if filter.ChatID != "" {
where += " AND chat_id = ?"
args = append(args, filter.ChatID)
}
if filter.Status != "" {
where += " AND status = ?"
args = append(args, string(filter.Status))
}
args = append(args, filter.Limit, filter.Offset)
rows, err := s.db.QueryContext(ctx, `
SELECT id, chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at
FROM tasks WHERE `+where+` ORDER BY updated_at DESC LIMIT ? OFFSET ?`, args...)
if err != nil {
return nil, fmt.Errorf("%w: list tasks: %w", ErrDB, err)
}
defer rows.Close()
var tasks []*Task
for rows.Next() {
t := &Task{}
if err := rows.Scan(&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo,
&t.Why, &t.AC, &t.TaskTag, &t.Status, &t.CreatedAt, &t.UpdatedAt); err != nil {
return nil, fmt.Errorf("%w: scan task: %w", ErrDB, err)
}
tasks = append(tasks, t)
}
return tasks, rows.Err()
}
// DeleteTask удаляет задачу (каскадно — трассы). Только для тестов/админки.
func (s *Storage) DeleteTask(ctx context.Context, id int64) error {
res, err := s.db.ExecContext(ctx, `DELETE FROM tasks WHERE id = ?`, id)
if err != nil {
return fmt.Errorf("%w: delete task %d: %w", ErrDB, id, err)
}
n, _ := res.RowsAffected()
if n == 0 {
return fmt.Errorf("%w: task %d", ErrNotFound, id)
}
return nil
}