255e8dcf71
Frontend C++ ImGui (main.cpp + 4 paneles) + backend Go (HTTP + SQLite + fsnotify + SSE). Reusa parse/scan/watch funcs del registry (issue 0130a).
138 lines
3.4 KiB
Go
138 lines
3.4 KiB
Go
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
|