rmm2/backend/db/tasks.go

326 lines
8.2 KiB
Go

package db
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/cynfo/rmm-backend/models"
)
func (d *Database) CreateTask(agentID, action, scheduledAt, webhookURL, createdBy string) (*models.Task, error) {
// Validate action
validActions := map[string]bool{"backup": true, "update": true}
if !validActions[action] {
return nil, fmt.Errorf("ungueltige Aktion: %s", action)
}
var schedTime time.Time
var err error
if scheduledAt == "" {
schedTime = time.Now()
} else {
schedTime, err = time.Parse(time.RFC3339, scheduledAt)
if err != nil {
return nil, fmt.Errorf("ungueltiges Datum: %w", err)
}
}
var task models.Task
err = d.db.QueryRow(`
INSERT INTO tasks (agent_id, action, scheduled_at, webhook_url, created_by)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, agent_id, action, status, scheduled_at, webhook_url, COALESCE(created_by, ''), created_at
`, agentID, action, schedTime, webhookURL, createdBy).Scan(
&task.ID, &task.AgentID, &task.Action, &task.Status,
&schedTime, &task.WebhookURL, &task.CreatedBy, &schedTime,
)
if err != nil {
return nil, fmt.Errorf("Task erstellen: %w", err)
}
// Re-read cleanly
return d.GetTask(task.ID)
}
func (d *Database) GetTask(id int) (*models.Task, error) {
var t models.Task
var scheduledAt, createdAt time.Time
var startedAt, finishedAt sql.NullTime
var resultJSON sql.NullString
var webhookURL, webhookResponse, createdBy sql.NullString
var agentName, customerName sql.NullString
err := d.db.QueryRow(`
SELECT t.id, t.agent_id, COALESCE(a.name, ''), COALESCE(c.name, ''),
t.action, t.status, t.scheduled_at, t.started_at, t.finished_at,
t.result, t.webhook_url, t.webhook_sent, t.webhook_response,
t.created_by, t.created_at
FROM tasks t
LEFT JOIN agents a ON a.id = t.agent_id
LEFT JOIN customers c ON c.id = a.customer_id
WHERE t.id = $1
`, id).Scan(
&t.ID, &t.AgentID, &agentName, &customerName,
&t.Action, &t.Status, &scheduledAt, &startedAt, &finishedAt,
&resultJSON, &webhookURL, &t.WebhookSent, &webhookResponse,
&createdBy, &createdAt,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
t.AgentName = agentName.String
t.CustomerName = customerName.String
t.ScheduledAt = scheduledAt.Format(time.RFC3339)
t.CreatedAt = createdAt.Format(time.RFC3339)
if startedAt.Valid {
s := startedAt.Time.Format(time.RFC3339)
t.StartedAt = &s
}
if finishedAt.Valid {
s := finishedAt.Time.Format(time.RFC3339)
t.FinishedAt = &s
}
if resultJSON.Valid {
var r interface{}
json.Unmarshal([]byte(resultJSON.String), &r)
t.Result = r
}
if webhookURL.Valid {
t.WebhookURL = webhookURL.String
}
if webhookResponse.Valid {
t.WebhookResponse = webhookResponse.String
}
if createdBy.Valid {
t.CreatedBy = createdBy.String
}
return &t, nil
}
func (d *Database) GetTasks(limit, offset int, agentID, status, action string) ([]models.Task, int, error) {
if limit <= 0 {
limit = 50
}
where := []string{}
args := []interface{}{}
argIdx := 1
if agentID != "" {
where = append(where, fmt.Sprintf("t.agent_id = $%d", argIdx))
args = append(args, agentID)
argIdx++
}
if status != "" {
where = append(where, fmt.Sprintf("t.status = $%d", argIdx))
args = append(args, status)
argIdx++
}
if action != "" {
where = append(where, fmt.Sprintf("t.action = $%d", argIdx))
args = append(args, action)
argIdx++
}
whereClause := ""
if len(where) > 0 {
whereClause = "WHERE " + strings.Join(where, " AND ")
}
// Count
var total int
countQuery := "SELECT COUNT(*) FROM tasks t " + whereClause
if err := d.db.QueryRow(countQuery, args...).Scan(&total); err != nil {
return nil, 0, err
}
// Data
query := fmt.Sprintf(`
SELECT t.id, t.agent_id, COALESCE(a.name, ''), COALESCE(c.name, ''),
t.action, t.status, t.scheduled_at, t.started_at, t.finished_at,
t.result, t.webhook_url, t.webhook_sent, t.webhook_response,
t.created_by, t.created_at
FROM tasks t
LEFT JOIN agents a ON a.id = t.agent_id
LEFT JOIN customers c ON c.id = a.customer_id
%s ORDER BY t.created_at DESC LIMIT $%d OFFSET $%d
`, whereClause, argIdx, argIdx+1)
args = append(args, limit, offset)
rows, err := d.db.Query(query, args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
var tasks []models.Task
for rows.Next() {
var t models.Task
var scheduledAt, createdAt time.Time
var startedAt, finishedAt sql.NullTime
var resultJSON sql.NullString
var webhookURL, webhookResponse, createdBy sql.NullString
var agentName, customerName sql.NullString
if err := rows.Scan(
&t.ID, &t.AgentID, &agentName, &customerName,
&t.Action, &t.Status, &scheduledAt, &startedAt, &finishedAt,
&resultJSON, &webhookURL, &t.WebhookSent, &webhookResponse,
&createdBy, &createdAt,
); err != nil {
return nil, 0, err
}
t.AgentName = agentName.String
t.CustomerName = customerName.String
t.ScheduledAt = scheduledAt.Format(time.RFC3339)
t.CreatedAt = createdAt.Format(time.RFC3339)
if startedAt.Valid {
s := startedAt.Time.Format(time.RFC3339)
t.StartedAt = &s
}
if finishedAt.Valid {
s := finishedAt.Time.Format(time.RFC3339)
t.FinishedAt = &s
}
if resultJSON.Valid {
var r interface{}
json.Unmarshal([]byte(resultJSON.String), &r)
t.Result = r
}
if webhookURL.Valid {
t.WebhookURL = webhookURL.String
}
if webhookResponse.Valid {
t.WebhookResponse = webhookResponse.String
}
if createdBy.Valid {
t.CreatedBy = createdBy.String
}
tasks = append(tasks, t)
}
if tasks == nil {
tasks = []models.Task{}
}
return tasks, total, rows.Err()
}
func (d *Database) UpdateTaskStatus(id int, status string, result interface{}) error {
var resultJSON *string
if result != nil {
b, err := json.Marshal(result)
if err == nil {
s := string(b)
resultJSON = &s
}
}
now := time.Now().UTC()
switch status {
case "running":
_, err := d.db.Exec(
"UPDATE tasks SET status = $1, started_at = $2 WHERE id = $3",
status, now, id,
)
return err
case "completed", "failed":
_, err := d.db.Exec(
"UPDATE tasks SET status = $1, finished_at = $2, result = $3 WHERE id = $4",
status, now, resultJSON, id,
)
return err
default:
_, err := d.db.Exec("UPDATE tasks SET status = $1 WHERE id = $2", status, id)
return err
}
}
func (d *Database) SetTaskWebhookResult(id int, sent bool, response string) error {
// Truncate response to 500 chars
if len(response) > 500 {
response = response[:500]
}
_, err := d.db.Exec(
"UPDATE tasks SET webhook_sent = $1, webhook_response = $2 WHERE id = $3",
sent, response, id,
)
return err
}
func (d *Database) GetDueTasks() ([]models.Task, error) {
rows, err := d.db.Query(`
SELECT t.id, t.agent_id, COALESCE(a.name, ''), COALESCE(c.name, ''),
t.action, t.status, t.scheduled_at, t.webhook_url, t.created_at
FROM tasks t
LEFT JOIN agents a ON a.id = t.agent_id
LEFT JOIN customers c ON c.id = a.customer_id
WHERE t.status = 'scheduled' AND t.scheduled_at <= NOW()
ORDER BY t.scheduled_at ASC
`)
if err != nil {
return nil, err
}
defer rows.Close()
var tasks []models.Task
for rows.Next() {
var t models.Task
var scheduledAt, createdAt time.Time
var webhookURL sql.NullString
var agentName, customerName sql.NullString
if err := rows.Scan(
&t.ID, &t.AgentID, &agentName, &customerName,
&t.Action, &t.Status, &scheduledAt, &webhookURL, &createdAt,
); err != nil {
return nil, err
}
t.AgentName = agentName.String
t.CustomerName = customerName.String
t.ScheduledAt = scheduledAt.Format(time.RFC3339)
t.CreatedAt = createdAt.Format(time.RFC3339)
if webhookURL.Valid {
t.WebhookURL = webhookURL.String
}
tasks = append(tasks, t)
}
return tasks, rows.Err()
}
func (d *Database) CancelTask(id int) error {
res, err := d.db.Exec(
"UPDATE tasks SET status = 'cancelled' WHERE id = $1 AND status = 'scheduled'",
id,
)
if err != nil {
return err
}
rows, _ := res.RowsAffected()
if rows == 0 {
return fmt.Errorf("Task nicht gefunden oder nicht mehr im Status 'scheduled'")
}
return nil
}
func (d *Database) DeleteTask(id int) error {
res, err := d.db.Exec("DELETE FROM tasks WHERE id = $1", id)
if err != nil {
return err
}
rows, _ := res.RowsAffected()
if rows == 0 {
return fmt.Errorf("Task nicht gefunden")
}
return nil
}