Wire demo seed so TUI connects to Test and Website Relaunch.

Dev seed now issues write tokens and writes local taskmaster meta under TASK_MASTER_HOME, so make run-dev can sync without manual setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 23:05:10 +01:00
co-authored by Cursor
parent 79b0821837
commit b311a355fd
5 changed files with 201 additions and 11 deletions
+111 -2
View File
@@ -3,16 +3,31 @@ package server
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"task/internal/config"
"task/internal/project"
"task/internal/sqlite"
"task/internal/ticket"
)
// SeedDemoOptions controls optional client wiring during demo seed.
type SeedDemoOptions struct {
// ClientDataDir, when set, writes local TUI projects (meta.json + tokens)
// for "Test" and "Website Relaunch" under that data directory.
ClientDataDir string
// BaseURL is the tm_server URL stored in client meta (default http://127.0.0.1:8080).
BaseURL string
}
// tuiSeedProjects are server project names that get a write token and local TUI wiring.
var tuiSeedProjects = []string{"Test", "Website Relaunch"}
// SeedDemo populates sample users/projects/tickets when the DB has no projects yet.
// No external backends (YouTrack etc.) — the web UI talks only to this tm_server.
func (s *Server) SeedDemo() error {
func (s *Server) SeedDemo(opts SeedDemoOptions) error {
projects, err := s.Projects.List()
if err != nil {
return err
@@ -58,7 +73,7 @@ func (s *Server) SeedDemo() error {
{Title: "Board & Login prüfen", Desc: "Mit admin/admin einloggen, Board öffnen, Status ändern.\n\n- [ ] Login\n- [ ] Ticket editieren\n- [ ] Timer starten", Status: ticket.StatusInProgress},
{Title: "Zeit auf Ticket buchen", Desc: "Im Ticket-Detail Timer oder manuelle Minuten buchen, dann Report prüfen.", Status: ticket.StatusTodo},
{Title: "Members & Rechte", Desc: "alice als Member ist schon angelegt — Memberships-Tab ansehen.", Status: ticket.StatusTodo},
{Title: "TUI anbinden", Desc: "Settings → Token (write) erzeugen → in `tm` Projekt-Edit backend=taskmaster eintragen (URL, Token, diese Project-ID).", Status: ticket.StatusTodo},
{Title: "TUI Sync", Desc: "Lokal unter `.dev-home` schon mit Token verdrahtet — in `tm` (`make run-dev`) Pull/Push testen.", Status: ticket.StatusTodo},
{Title: "Seed abgeschlossen", Desc: "Beispiel-Ticket im Status DONE.", Status: ticket.StatusDone},
},
},
@@ -157,5 +172,99 @@ func (s *Server) SeedDemo() error {
}
fmt.Fprintf(os.Stderr, "seeded demo: %d projects, 2 groups (users %s + alice/alice), sync=none\n", len(demos), admin.Username)
if dir := strings.TrimSpace(opts.ClientDataDir); dir != "" {
if err := s.wireDemoTUI(dir, opts.BaseURL, created, admin.ID); err != nil {
return fmt.Errorf("seed tui: %w", err)
}
}
return nil
}
func (s *Server) wireDemoTUI(dataDir, baseURL string, created []struct{ ID, Name string }, adminID string) error {
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
if baseURL == "" {
baseURL = "http://127.0.0.1:8080"
}
byName := make(map[string]string, len(created))
for _, c := range created {
byName[c.Name] = c.ID
}
store := project.NewDirStore(dataDir)
wired := make([]string, 0, len(tuiSeedProjects))
for _, name := range tuiSeedProjects {
remoteID, ok := byName[name]
if !ok {
return fmt.Errorf("missing seeded project %q", name)
}
tok, err := s.DB.CreateProjectToken(remoteID, "dev-tui", sqlite.TokenScopeWrite, adminID)
if err != nil {
return fmt.Errorf("token for %s: %w", name, err)
}
// Fresh local dir so stale tickets from a previous DB wipe cannot confuse sync.
_ = os.RemoveAll(filepath.Join(dataDir, "projects", name))
meta := project.Meta{
Backend: project.BackendTaskMaster,
TaskMaster: &project.TaskMaster{
BaseURL: baseURL,
Token: tok.Token,
ProjectID: remoteID,
},
}
if err := store.SaveMeta(name, meta); err != nil {
return fmt.Errorf("meta %s: %w", name, err)
}
wired = append(wired, name)
fmt.Fprintf(os.Stderr, "tui wired: %s → %s (project %s, token %s…)\n",
name, baseURL, remoteID, trimTokenPrefix(tok.Token))
}
if err := setActiveProjectIfSameDataDir(dataDir, tuiSeedProjects[0]); err != nil {
return err
}
fmt.Fprintf(os.Stderr, "tui ready: %s (make run-dev → Y pull)\n", strings.Join(wired, ", "))
return nil
}
func trimTokenPrefix(tok string) string {
if len(tok) <= 10 {
return tok
}
return tok[:10]
}
func setActiveProjectIfSameDataDir(dataDir, active string) error {
cfg, err := config.LoadOrCreate()
if err != nil {
return fmt.Errorf("config: %w", err)
}
if filepath.Clean(cfg.DataDir) != filepath.Clean(dataDir) {
return nil
}
cfg.ActiveProject = active
if err := config.Save(cfg); err != nil {
return fmt.Errorf("save config: %w", err)
}
return nil
}
// SeedBaseURL derives the public base URL for seeded TUI meta from -listen / env.
func SeedBaseURL(listen string) string {
if u := strings.TrimSpace(os.Getenv("TM_SEED_BASE_URL")); u != "" {
return strings.TrimRight(u, "/")
}
host := strings.TrimSpace(listen)
if host == "" {
host = ":8080"
}
switch {
case strings.HasPrefix(host, "http://"), strings.HasPrefix(host, "https://"):
return strings.TrimRight(host, "/")
case strings.HasPrefix(host, ":"):
return "http://127.0.0.1" + host
default:
return "http://" + host
}
}