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, } }