Add tm_server with SQLite, Svelte UI, and task-master TUI sync.
Introduces the web backend (auth, RBAC, project tokens, revisions), YouTrack-or-taskmaster backend choice in the TUI, and a first-pass embedded Svelte UI. The frontend is functional but needs a major rework. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Revision is a versioned snapshot of an entity.
|
||||
type Revision struct {
|
||||
ID int64 `json:"id"`
|
||||
EntityType string `json:"entityType"`
|
||||
EntityID string `json:"entityId"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Rev int `json:"rev"`
|
||||
At time.Time `json:"at"`
|
||||
ActorUserID string `json:"actorUserId,omitempty"`
|
||||
Op string `json:"op"`
|
||||
Snapshot json.RawMessage `json:"snapshot"`
|
||||
}
|
||||
|
||||
// AppendRevision stores a new revision snapshot.
|
||||
func (db *DB) AppendRevision(entityType, entityID, projectID, op, actorUserID string, snapshot any) (Revision, error) {
|
||||
data, err := json.Marshal(snapshot)
|
||||
if err != nil {
|
||||
return Revision{}, err
|
||||
}
|
||||
var maxRev sql.NullInt64
|
||||
_ = db.SQL.QueryRow(`SELECT MAX(rev) FROM revisions WHERE entity_type = ? AND entity_id = ?`,
|
||||
entityType, entityID).Scan(&maxRev)
|
||||
rev := 1
|
||||
if maxRev.Valid {
|
||||
rev = int(maxRev.Int64) + 1
|
||||
}
|
||||
at := time.Now().UTC()
|
||||
var actor any
|
||||
if actorUserID != "" {
|
||||
actor = actorUserID
|
||||
}
|
||||
res, err := db.SQL.Exec(`
|
||||
INSERT INTO revisions(entity_type, entity_id, project_id, rev, at, actor_user_id, op, snapshot_json)
|
||||
VALUES(?,?,?,?,?,?,?,?)`,
|
||||
entityType, entityID, projectID, rev, formatTime(at), actor, op, string(data))
|
||||
if err != nil {
|
||||
return Revision{}, err
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
return Revision{
|
||||
ID: id,
|
||||
EntityType: entityType,
|
||||
EntityID: entityID,
|
||||
ProjectID: projectID,
|
||||
Rev: rev,
|
||||
At: at,
|
||||
ActorUserID: actorUserID,
|
||||
Op: op,
|
||||
Snapshot: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListRevisions returns revisions for an entity, newest first.
|
||||
func (db *DB) ListRevisions(entityType, entityID string) ([]Revision, error) {
|
||||
rows, err := db.SQL.Query(`
|
||||
SELECT id, entity_type, entity_id, project_id, rev, at, actor_user_id, op, snapshot_json
|
||||
FROM revisions WHERE entity_type = ? AND entity_id = ?
|
||||
ORDER BY rev DESC`, entityType, entityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanRevisions(rows)
|
||||
}
|
||||
|
||||
// GetRevision returns one revision by id.
|
||||
func (db *DB) GetRevision(id int64) (Revision, error) {
|
||||
row := db.SQL.QueryRow(`
|
||||
SELECT id, entity_type, entity_id, project_id, rev, at, actor_user_id, op, snapshot_json
|
||||
FROM revisions WHERE id = ?`, id)
|
||||
var r Revision
|
||||
var at string
|
||||
var actor sql.NullString
|
||||
var snap string
|
||||
err := row.Scan(&r.ID, &r.EntityType, &r.EntityID, &r.ProjectID, &r.Rev, &at, &actor, &r.Op, &snap)
|
||||
if err == sql.ErrNoRows {
|
||||
return Revision{}, fmt.Errorf("revision %d: not found", id)
|
||||
}
|
||||
if err != nil {
|
||||
return Revision{}, err
|
||||
}
|
||||
r.At = parseTime(at)
|
||||
if actor.Valid {
|
||||
r.ActorUserID = actor.String
|
||||
}
|
||||
r.Snapshot = json.RawMessage(snap)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func scanRevisions(rows *sql.Rows) ([]Revision, error) {
|
||||
var out []Revision
|
||||
for rows.Next() {
|
||||
var r Revision
|
||||
var at string
|
||||
var actor sql.NullString
|
||||
var snap string
|
||||
if err := rows.Scan(&r.ID, &r.EntityType, &r.EntityID, &r.ProjectID, &r.Rev, &at, &actor, &r.Op, &snap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.At = parseTime(at)
|
||||
if actor.Valid {
|
||||
r.ActorUserID = actor.String
|
||||
}
|
||||
r.Snapshot = json.RawMessage(snap)
|
||||
out = append(out, r)
|
||||
}
|
||||
if out == nil {
|
||||
out = []Revision{}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user