feat: initial scaffold of kanban_cpp v2 (issue 0130)
Frontend C++ ImGui (main.cpp + 4 paneles) + backend Go (HTTP + SQLite + fsnotify + SSE). Reusa parse/scan/watch funcs del registry (issue 0130a).
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fn-registry/functions/infra"
|
||||
)
|
||||
|
||||
func (s *Server) ingestAll() error {
|
||||
issues, err := infra.ScanIssuesDir(s.issuesDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, iss := range issues {
|
||||
if err := s.upsertIssueRow(iss); err != nil {
|
||||
log.Printf("upsert issue %s: %v", iss.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
flows, err := infra.ScanFlowsDir(s.flowsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fl := range flows {
|
||||
if err := s.upsertFlowRow(fl); err != nil {
|
||||
log.Printf("upsert flow %s: %v", fl.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("ingested %d issues, %d flows", len(issues), len(flows))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) upsertIssueRow(iss infra.Issue) error {
|
||||
body, err := readBody(iss.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO issues (id,title,status,type,scope,priority,domain_json,tags_json,depends_json,blocks_json,related_json,flow_id,body,file_path,mtime_ns,created_at,updated_at,completed)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
title=excluded.title, status=excluded.status, type=excluded.type, scope=excluded.scope,
|
||||
priority=excluded.priority, domain_json=excluded.domain_json, tags_json=excluded.tags_json,
|
||||
depends_json=excluded.depends_json, blocks_json=excluded.blocks_json, related_json=excluded.related_json,
|
||||
flow_id=excluded.flow_id, body=excluded.body, file_path=excluded.file_path, mtime_ns=excluded.mtime_ns,
|
||||
created_at=excluded.created_at, updated_at=excluded.updated_at, completed=excluded.completed
|
||||
`,
|
||||
iss.ID, iss.Title, iss.Status, iss.Type, iss.Scope, iss.Priority,
|
||||
jsonOrEmpty(iss.Domain), jsonOrEmpty(iss.Tags),
|
||||
jsonOrEmpty(iss.Depends), jsonOrEmpty(iss.Blocks), jsonOrEmpty(iss.Related),
|
||||
iss.Flow, string(body), iss.FilePath, iss.MtimeNs,
|
||||
iss.Created, iss.Updated, boolInt(iss.Completed),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) upsertFlowRow(fl infra.Flow) error {
|
||||
body, err := readBody(fl.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO flows (id,title,status,kind,tags_json,body,file_path,mtime_ns)
|
||||
VALUES (?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
title=excluded.title, status=excluded.status, kind=excluded.kind,
|
||||
tags_json=excluded.tags_json, body=excluded.body, file_path=excluded.file_path, mtime_ns=excluded.mtime_ns
|
||||
`,
|
||||
fl.ID, fl.Title, fl.Status, fl.Kind, jsonOrEmpty(fl.Tags),
|
||||
string(body), fl.FilePath, fl.MtimeNs,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func readBody(path string) ([]byte, error) {
|
||||
_, body, err := infra.ParseIssueMd(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func jsonOrEmpty(xs []string) string {
|
||||
if len(xs) == 0 {
|
||||
return "[]"
|
||||
}
|
||||
b, _ := json.Marshal(xs)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func boolInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *Server) deleteIssue(id string) error {
|
||||
_, err := s.db.Exec(`DELETE FROM issues WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) deleteFlow(id string) error {
|
||||
_, err := s.db.Exec(`DELETE FROM flows WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func issueIDFromPath(path, issuesDir string) string {
|
||||
rel, err := filepath.Rel(issuesDir, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
base := filepath.Base(rel)
|
||||
if !strings.HasSuffix(base, ".md") {
|
||||
return ""
|
||||
}
|
||||
name := strings.TrimSuffix(base, ".md")
|
||||
for i, r := range name {
|
||||
if r == '-' {
|
||||
return name[:i]
|
||||
}
|
||||
if r < '0' || r > '9' {
|
||||
if i == 0 {
|
||||
return ""
|
||||
}
|
||||
return name[:i]
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var _ = sql.ErrNoRows
|
||||
Reference in New Issue
Block a user