package server import ( "fmt" "os" "time" "task/internal/sqlite" "task/internal/ticket" ) // 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 { projects, err := s.Projects.List() if err != nil { return err } if len(projects) > 0 { return nil } users, err := s.DB.ListUsers() if err != nil { return err } if len(users) == 0 { return fmt.Errorf("seed: no users (bootstrap admin first)") } admin := users[0] for _, u := range users { if u.IsAdmin { admin = u break } } member, err := s.DB.CreateUser("alice", "alice", false) if err != nil { member, err = s.DB.GetUserByUsername("alice") if err != nil { return fmt.Errorf("seed alice: %w", err) } } type seedTicket struct { Title, Desc string Status ticket.Status } demos := []struct { Name string Tickets []seedTicket }{ { Name: "Test", Tickets: []seedTicket{ {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: "Seed abgeschlossen", Desc: "Beispiel-Ticket im Status DONE.", Status: ticket.StatusDone}, }, }, { Name: "Website Relaunch", Tickets: []seedTicket{ {Title: "Landing Hero entwerfen", Desc: "Dunkles Layout, klare CTAs.\n\n- [ ] Moodboard\n- [ ] Wireframe", Status: ticket.StatusInProgress}, {Title: "CMS-Inhalte migrieren", Desc: "Alte Seiten nach Markdown überführen.", Status: ticket.StatusTodo}, {Title: "Impressum & Datenschutz", Desc: "Rechtstexte finalisieren.", Status: ticket.StatusDone}, }, }, { Name: "API Platform", Tickets: []seedTicket{ {Title: "Auth-Middleware härten", Desc: "Session-Cookies dokumentieren.", Status: ticket.StatusInProgress}, {Title: "Rate-Limits festlegen", Desc: "Pro User und pro IP.", Status: ticket.StatusTodo}, {Title: "Health-Check", Desc: "Bereits live.", Status: ticket.StatusDone}, }, }, } now := time.Now().UTC() for _, d := range demos { p, err := s.Projects.Create("", d.Name) if err != nil { return err } _ = s.DB.SetMember(p.ID, admin.ID, sqlite.RoleOwner) _ = s.DB.SetMember(p.ID, member.ID, sqlite.RoleMember) // Explicit local-only sync (no remote backend in the test environment). _ = s.Projects.SetSyncBackend(sqlite.SyncBackend{ ProjectID: p.ID, Kind: "none", }) var firstID string for i, st := range d.Tickets { t, err := s.Tickets.Create(ticket.Ticket{ Title: st.Title, Description: st.Desc, Status: st.Status, ProjectID: p.ID, }) if err != nil { return err } _, _ = s.DB.AppendRevision("ticket", t.ID, p.ID, "seed", admin.ID, t) if i == 0 { firstID = t.ID } } if firstID != "" { _, err := s.Times.Create(ticket.TimeEntry{ TicketID: firstID, ProjectID: p.ID, Start: now.Add(-2 * time.Hour), End: now.Add(-90 * time.Minute), Duration: 30 * time.Minute, Source: ticket.SourceManual, Note: "seed", }) if err != nil { return err } } } fmt.Fprintf(os.Stderr, "seeded demo: Test + sample projects (users %s + alice/alice), sync=none\n", admin.Username) return nil }