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