feat: add JSON serialization, urgency field, and snake_case API contract

Fix latent API bug where multi-word fields (RecurrenceDuration, ParentUUID,
CreatedAt) serialized as PascalCase, breaking the frontend. Add explicit
snake_case json tags and custom MarshalJSON/UnmarshalJSON on Task, Status,
and APIKey to emit unix timestamps and string status codes.

Add Urgency float64 as a derived field on Task, populated via
PopulateUrgency helper in all handlers before serialization. The report
engine's sortByUrgency now also retains the computed score.

Frontend updated with urgency type, color-coded badge in TaskItem, and
mock data values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 14:58:34 +01:00
parent 924b66bc64
commit 3bb2ef2759
9 changed files with 581 additions and 51 deletions
+64 -6
View File
@@ -3,6 +3,7 @@ package engine
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"time"
@@ -11,12 +12,69 @@ import (
// APIKey represents an API key in the database
type APIKey struct {
ID int
Name string
UserID int
CreatedAt time.Time
LastUsed *time.Time
Revoked bool
ID int `json:"id"`
Name string `json:"name"`
UserID int `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
LastUsed *time.Time `json:"last_used,omitempty"`
Revoked bool `json:"revoked"`
}
// MarshalJSON emits APIKey with unix timestamps.
func (k APIKey) MarshalJSON() ([]byte, error) {
type keyJSON struct {
ID int `json:"id"`
Name string `json:"name"`
UserID int `json:"user_id"`
CreatedAt int64 `json:"created_at"`
LastUsed *int64 `json:"last_used,omitempty"`
Revoked bool `json:"revoked"`
}
var lastUsed *int64
if k.LastUsed != nil {
v := k.LastUsed.Unix()
lastUsed = &v
}
return json.Marshal(keyJSON{
ID: k.ID,
Name: k.Name,
UserID: k.UserID,
CreatedAt: k.CreatedAt.Unix(),
LastUsed: lastUsed,
Revoked: k.Revoked,
})
}
// UnmarshalJSON parses APIKey from JSON with unix timestamps.
func (k *APIKey) UnmarshalJSON(data []byte) error {
type keyJSON struct {
ID int `json:"id"`
Name string `json:"name"`
UserID int `json:"user_id"`
CreatedAt int64 `json:"created_at"`
LastUsed *int64 `json:"last_used,omitempty"`
Revoked bool `json:"revoked"`
}
var raw keyJSON
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
k.ID = raw.ID
k.Name = raw.Name
k.UserID = raw.UserID
k.CreatedAt = time.Unix(raw.CreatedAt, 0)
k.Revoked = raw.Revoked
if raw.LastUsed != nil {
t := time.Unix(*raw.LastUsed, 0)
k.LastUsed = &t
}
return nil
}
// GenerateAPIKey creates a new API key for the given name