1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package task
6
7import (
8 "encoding/hex"
9 "errors"
10 "strings"
11 "time"
12
13 "github.com/zeebo/blake3"
14)
15
16// ErrNotFound indicates that a task with the given ID is absent.
17var ErrNotFound = errors.New("task: not found")
18
19// ErrExists indicates that a task already exists when attempting to create it.
20var ErrExists = errors.New("task: already exists")
21
22// ErrEmptyTitle indicates that a title was missing when required.
23var ErrEmptyTitle = errors.New("task: title is required")
24
25// ErrInvalidStatus is returned when a status string is not recognised.
26var ErrInvalidStatus = errors.New("task: invalid status")
27
28// Status captures the lifecycle of a task.
29type Status string
30
31const (
32 StatusPending Status = "pending"
33 StatusInProgress Status = "in_progress"
34 StatusCompleted Status = "completed"
35 StatusFailed Status = "failed"
36 StatusCancelled Status = "cancelled"
37)
38
39// AllStatuses returns the known task statuses.
40func AllStatuses() []Status {
41 return []Status{
42 StatusPending,
43 StatusInProgress,
44 StatusCompleted,
45 StatusFailed,
46 StatusCancelled,
47 }
48}
49
50// Valid reports whether s matches a known status.
51func (s Status) Valid() bool {
52 switch s {
53 case StatusPending,
54 StatusInProgress,
55 StatusCompleted,
56 StatusFailed,
57 StatusCancelled:
58 return true
59 default:
60 return false
61 }
62}
63
64func (s Status) String() string {
65 return string(s)
66}
67
68// ParseStatus converts a string to a Status value.
69func ParseStatus(v string) (Status, error) {
70 status := Status(v)
71 if !status.Valid() {
72 return "", ErrInvalidStatus
73 }
74 return status, nil
75}
76
77// Task represents an individual unit of work tracked within a session.
78type Task struct {
79 ID string `json:"id"`
80 Title string `json:"title"`
81 Description string `json:"description"`
82 Status Status `json:"status"`
83 CreatedAt time.Time `json:"created_at"`
84 UpdatedAt time.Time `json:"updated_at"`
85 CreatedSeq uint64 `json:"created_seq"`
86}
87
88// GenerateID deterministically produces a task identifier for the given
89// session, title, and description.
90func GenerateID(sessionID, title, description string) string {
91 normalized := normalizeForHash(title) + "|" + normalizeForHash(description) + "|" + sessionID
92
93 sum := blake3.Sum256([]byte(normalized))
94 hexed := hex.EncodeToString(sum[:])
95 return hexed[:6]
96}
97
98func normalizeForHash(s string) string {
99 trimmed := strings.TrimSpace(s)
100 if trimmed == "" {
101 return ""
102 }
103 return strings.Join(strings.Fields(trimmed), " ")
104}