Files
task-master/internal/syncbackend/backend.go
T
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

123 lines
3.3 KiB
Go

package syncbackend
import (
"context"
"fmt"
"task/internal/project"
"task/internal/ticket"
"task/internal/ytsync"
)
// RemoteProject is a project discovered on a remote backend.
type RemoteProject struct {
Key string `json:"key"`
Name string `json:"name"`
ID string `json:"id,omitempty"`
}
// Connection credentials for a remote instance or project.
type Connection struct {
Kind string
BaseURL string
Token string
Scope string
}
// ProjectConfig is the per-local-project sync settings.
type ProjectConfig struct {
Kind string
BaseURL string
Token string
ProjectKey string
Query string
}
// Result reuses ytsync.Result for pull/push summaries.
type Result = ytsync.Result
// Backend syncs tickets with an external system.
type Backend interface {
Kind() string
ListProjects(ctx context.Context, conn Connection) ([]RemoteProject, error)
Pull(ctx context.Context, projectID string, cfg ProjectConfig, store ticket.Store, timeStore ticket.TimeStore) (Result, error)
Push(ctx context.Context, projectID string, cfg ProjectConfig, store ticket.Store, timeStore ticket.TimeStore) (Result, error)
}
// Registry maps kind → Backend.
type Registry struct {
backends map[string]Backend
}
// NewRegistry returns a registry with built-in backends.
func NewRegistry() *Registry {
r := &Registry{backends: map[string]Backend{}}
r.Register(&NoneBackend{})
r.Register(&YouTrackBackend{})
return r
}
// Register adds a backend.
func (r *Registry) Register(b Backend) {
r.backends[b.Kind()] = b
}
// Get returns a backend by kind.
func (r *Registry) Get(kind string) (Backend, error) {
if kind == "" {
kind = "none"
}
b, ok := r.backends[kind]
if !ok {
return nil, fmt.Errorf("unknown sync backend %q", kind)
}
return b, nil
}
// NoneBackend disables sync.
type NoneBackend struct{}
func (NoneBackend) Kind() string { return "none" }
func (NoneBackend) ListProjects(context.Context, Connection) ([]RemoteProject, error) {
return nil, fmt.Errorf("no remote projects for kind none")
}
func (NoneBackend) Pull(context.Context, string, ProjectConfig, ticket.Store, ticket.TimeStore) (Result, error) {
return Result{}, fmt.Errorf("sync disabled")
}
func (NoneBackend) Push(context.Context, string, ProjectConfig, ticket.Store, ticket.TimeStore) (Result, error) {
return Result{}, fmt.Errorf("sync disabled")
}
// YouTrackBackend wraps existing ytsync.
type YouTrackBackend struct{}
func (YouTrackBackend) Kind() string { return "youtrack" }
func (YouTrackBackend) ListProjects(ctx context.Context, conn Connection) ([]RemoteProject, error) {
return ListYouTrackProjects(ctx, conn.BaseURL, conn.Token)
}
func (YouTrackBackend) Pull(ctx context.Context, projectID string, cfg ProjectConfig, store ticket.Store, timeStore ticket.TimeStore) (Result, error) {
yt := toYouTrack(cfg)
s := &ytsync.YouTrackSyncer{Store: store, TimeStore: timeStore}
return s.Pull(ctx, projectID, yt)
}
func (YouTrackBackend) Push(ctx context.Context, projectID string, cfg ProjectConfig, store ticket.Store, timeStore ticket.TimeStore) (Result, error) {
yt := toYouTrack(cfg)
s := &ytsync.YouTrackSyncer{Store: store, TimeStore: timeStore}
return s.Push(ctx, projectID, yt)
}
func toYouTrack(cfg ProjectConfig) project.YouTrack {
return project.YouTrack{
BaseURL: cfg.BaseURL,
Token: cfg.Token,
ProjectKey: cfg.ProjectKey,
Query: cfg.Query,
}
}