Files
skrueckenandCursor 4adfcdb95a 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>
2026-10-27 22:23:11 +01:00

119 lines
3.2 KiB
Go

package sqlite
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/google/uuid"
)
// Connection is a remote backend credential (instance or project scope).
type Connection struct {
ID string `json:"id"`
Name string `json:"name"`
Kind string `json:"kind"`
BaseURL string `json:"baseUrl"`
Scope string `json:"scope"` // instance | project
CreatedBy string `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
Token string `json:"-"` // only populated when decrypted for sync
}
// CreateConnection stores an encrypted token.
func (db *DB) CreateConnection(name, kind, baseURL, scope, token, createdBy string) (Connection, error) {
name = strings.TrimSpace(name)
kind = strings.TrimSpace(kind)
baseURL = strings.TrimSpace(baseURL)
scope = strings.TrimSpace(scope)
if name == "" || kind == "" || baseURL == "" || token == "" {
return Connection{}, fmt.Errorf("name, kind, baseUrl and token required")
}
if scope == "" {
scope = "instance"
}
enc, err := db.EncryptToken(token)
if err != nil {
return Connection{}, err
}
c := Connection{
ID: uuid.NewString(),
Name: name,
Kind: kind,
BaseURL: baseURL,
Scope: scope,
CreatedBy: createdBy,
CreatedAt: time.Now().UTC(),
}
_, err = db.SQL.Exec(`
INSERT INTO connections(id, name, kind, base_url, scope, encrypted_token, created_by, created_at)
VALUES(?,?,?,?,?,?,?,?)`,
c.ID, c.Name, c.Kind, c.BaseURL, c.Scope, enc, c.CreatedBy, formatTime(c.CreatedAt))
if err != nil {
return Connection{}, err
}
return c, nil
}
// ListConnections returns connections without tokens.
func (db *DB) ListConnections() ([]Connection, error) {
rows, err := db.SQL.Query(`
SELECT id, name, kind, base_url, scope, created_by, created_at
FROM connections ORDER BY name COLLATE NOCASE`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Connection
for rows.Next() {
var c Connection
var created string
if err := rows.Scan(&c.ID, &c.Name, &c.Kind, &c.BaseURL, &c.Scope, &c.CreatedBy, &created); err != nil {
return nil, err
}
c.CreatedAt = parseTime(created)
out = append(out, c)
}
if out == nil {
out = []Connection{}
}
return out, rows.Err()
}
// GetConnection loads a connection and decrypts its token.
func (db *DB) GetConnection(id string) (Connection, error) {
var c Connection
var created string
var blob []byte
err := db.SQL.QueryRow(`
SELECT id, name, kind, base_url, scope, encrypted_token, created_by, created_at
FROM connections WHERE id = ?`, id).
Scan(&c.ID, &c.Name, &c.Kind, &c.BaseURL, &c.Scope, &blob, &c.CreatedBy, &created)
if err == sql.ErrNoRows {
return Connection{}, fmt.Errorf("connection %s: not found", id)
}
if err != nil {
return Connection{}, err
}
c.CreatedAt = parseTime(created)
c.Token, err = db.DecryptToken(blob)
if err != nil {
return Connection{}, err
}
return c, nil
}
// DeleteConnection removes a connection.
func (db *DB) DeleteConnection(id string) error {
res, err := db.SQL.Exec(`DELETE FROM connections WHERE id = ?`, id)
if err != nil {
return err
}
n, _ := res.RowsAffected()
if n == 0 {
return fmt.Errorf("connection %s: not found", id)
}
return nil
}