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:
@@ -0,0 +1,130 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user