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:
@@ -10,6 +10,12 @@ import (
|
||||
|
||||
const metaFileName = "meta.json"
|
||||
|
||||
const (
|
||||
BackendNone = "none"
|
||||
BackendYouTrack = "youtrack"
|
||||
BackendTaskMaster = "taskmaster"
|
||||
)
|
||||
|
||||
// YouTrack holds remote sync settings for a project.
|
||||
type YouTrack struct {
|
||||
BaseURL string `json:"baseUrl"`
|
||||
@@ -34,9 +40,59 @@ func (y YouTrack) IssueQuery() string {
|
||||
return "project: " + strings.TrimSpace(y.ProjectKey)
|
||||
}
|
||||
|
||||
// TaskMaster holds sync settings for a remote tm_server project.
|
||||
type TaskMaster struct {
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Token string `json:"token"`
|
||||
ProjectID string `json:"projectId"`
|
||||
}
|
||||
|
||||
// Configured reports whether task-master sync can run.
|
||||
func (t TaskMaster) Configured() bool {
|
||||
return strings.TrimSpace(t.BaseURL) != "" &&
|
||||
strings.TrimSpace(t.Token) != "" &&
|
||||
strings.TrimSpace(t.ProjectID) != ""
|
||||
}
|
||||
|
||||
// Meta is per-project metadata stored beside tickets.json.
|
||||
type Meta struct {
|
||||
YouTrack *YouTrack `json:"youtrack,omitempty"`
|
||||
Backend string `json:"backend,omitempty"` // none|youtrack|taskmaster
|
||||
YouTrack *YouTrack `json:"youtrack,omitempty"`
|
||||
TaskMaster *TaskMaster `json:"taskmaster,omitempty"`
|
||||
}
|
||||
|
||||
// EffectiveBackend returns the sync backend kind, with legacy youtrack inference.
|
||||
func (m Meta) EffectiveBackend() string {
|
||||
b := strings.TrimSpace(strings.ToLower(m.Backend))
|
||||
switch b {
|
||||
case BackendYouTrack, BackendTaskMaster, BackendNone:
|
||||
return b
|
||||
}
|
||||
if m.YouTrack != nil && m.YouTrack.Configured() {
|
||||
return BackendYouTrack
|
||||
}
|
||||
if m.TaskMaster != nil && m.TaskMaster.Configured() {
|
||||
return BackendTaskMaster
|
||||
}
|
||||
if m.YouTrack != nil {
|
||||
return BackendYouTrack
|
||||
}
|
||||
if m.TaskMaster != nil {
|
||||
return BackendTaskMaster
|
||||
}
|
||||
return BackendNone
|
||||
}
|
||||
|
||||
// SyncConfigured reports whether the active backend is fully set up.
|
||||
func (m Meta) SyncConfigured() bool {
|
||||
switch m.EffectiveBackend() {
|
||||
case BackendYouTrack:
|
||||
return m.YouTrack != nil && m.YouTrack.Configured()
|
||||
case BackendTaskMaster:
|
||||
return m.TaskMaster != nil && m.TaskMaster.Configured()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DirStore) metaPath(id string) string {
|
||||
@@ -63,6 +119,12 @@ func (s *DirStore) LoadMeta(id string) (Meta, error) {
|
||||
if err := json.Unmarshal(data, &meta); err != nil {
|
||||
return Meta{}, fmt.Errorf("parse meta %s: %w", id, err)
|
||||
}
|
||||
if meta.Backend == "" && meta.YouTrack != nil {
|
||||
meta.Backend = BackendYouTrack
|
||||
}
|
||||
if meta.Backend == "" && meta.TaskMaster != nil {
|
||||
meta.Backend = BackendTaskMaster
|
||||
}
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
@@ -74,6 +136,9 @@ func (s *DirStore) SaveMeta(id string, meta Meta) error {
|
||||
if err := s.Ensure(id); err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.Backend == "" {
|
||||
meta.Backend = meta.EffectiveBackend()
|
||||
}
|
||||
path := s.metaPath(id)
|
||||
data, err := json.MarshalIndent(meta, "", " ")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user