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>
123 lines
3.3 KiB
Go
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,
|
|
}
|
|
}
|