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,118 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user