package storage import ( "database/sql" "encoding/json" "time" "safesight-control/internal/models" ) type TasksRepo struct { db *sql.DB } func NewTasksRepo(db *sql.DB) *TasksRepo { return &TasksRepo{db: db} } func (r *TasksRepo) Save(task *models.Task) error { if r == nil || r.db == nil || task == nil { return nil } task.Mu.RLock() payload, err := json.Marshal(task.Payload) if err != nil { task.Mu.RUnlock() return err } status := task.Status devices := make([]models.DeviceTaskStatus, 0, len(task.Devices)) for _, ds := range task.Devices { if ds == nil { continue } devices = append(devices, models.DeviceTaskStatus{ DeviceID: ds.DeviceID, Status: ds.Status, Progress: ds.Progress, Error: ds.Error, }) } task.Mu.RUnlock() now := time.Now().Format(time.RFC3339) finishedAt := "" if status == models.TaskSuccess || status == models.TaskFailed { finishedAt = now } // Update task row (atomic, no transaction needed). _, err = r.db.Exec(` INSERT INTO tasks(task_id, type, payload_json, status, created_at, finished_at) VALUES(?, ?, ?, ?, COALESCE((SELECT created_at FROM tasks WHERE task_id = ?), ?), ?) ON CONFLICT(task_id) DO UPDATE SET type=excluded.type, payload_json=excluded.payload_json, status=excluded.status, finished_at=excluded.finished_at `, task.ID, task.Type, string(payload), string(status), task.ID, now, finishedAt) if err != nil { return err } // Update device rows (separate atomic ops — no transaction needed). if len(devices) > 0 { if _, err := r.db.Exec(`DELETE FROM task_devices WHERE task_id = ?`, task.ID); err != nil { return err } for _, ds := range devices { if _, err := r.db.Exec(` INSERT INTO task_devices(task_id, device_id, status, progress, error_text) VALUES(?, ?, ?, ?, ?) `, task.ID, ds.DeviceID, string(ds.Status), ds.Progress, ds.Error); err != nil { return err } } } return nil } func (r *TasksRepo) List() ([]models.Task, error) { if r == nil || r.db == nil { return nil, nil } rows, err := r.db.Query(` SELECT task_id, type, payload_json, status, COALESCE(created_at,''), COALESCE(finished_at,'') FROM tasks ORDER BY created_at DESC, task_id DESC `) if err != nil { return nil, err } defer rows.Close() var out []models.Task for rows.Next() { var ( id, tType, payloadJSON, status string ) var createdAt, finishedAt string if err := rows.Scan(&id, &tType, &payloadJSON, &status, &createdAt, &finishedAt); err != nil { return nil, err } var payload any if payloadJSON != "" { if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil { return nil, err } } task := models.Task{ ID: id, Type: tType, Payload: payload, Status: models.TaskStatus(status), CreatedAt: createdAt, CompletedAt: finishedAt, Devices: map[string]*models.DeviceTaskStatus{}, } deviceRows, err := r.db.Query(` SELECT device_id, status, progress, error_text FROM task_devices WHERE task_id = ? ORDER BY rowid ASC `, id) if err != nil { return nil, err } for deviceRows.Next() { var did, dsStatus, errText string var progress float64 if err := deviceRows.Scan(&did, &dsStatus, &progress, &errText); err != nil { deviceRows.Close() return nil, err } task.DeviceIDs = append(task.DeviceIDs, did) task.Devices[did] = &models.DeviceTaskStatus{ DeviceID: did, Status: models.TaskStatus(dsStatus), Progress: progress, Error: errText, } } deviceRows.Close() out = append(out, task) } return out, rows.Err() }