Files
geniusrun/backend/cmd/dumpschema/main.go
Christophe Vila 6effb79097 refactor(log): unified schema — mandatory type/class/method, request_id removed
Every log record now carries type ('http' for the request middleware
line, 'wrapper' for garmin execute() calls and forwarded wrapper.py
stderr, 'app' for everything else), class (Go type with package, or the
package/module for free functions), and method (the emitting Go/Python
function -- wrapper.py stamps it automatically, so its msg prefixes are
gone). msg is optional and omitted when empty; the redundant messages
('HTTP request', 'wrapper call', 'garmin wrapper stderr') are dropped,
the HTTP verb moves to http_method, source=garmin-wrapper is replaced by
type=wrapper, and the request_id middleware plumbing (applog
WithLogger/FromContext) is removed. applog.App(class, method) is the
tagging helper for app records.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:45:30 +02:00

105 lines
2.7 KiB
Go

// Command dumpschema regenerates docs/DATABASE.md from the real, live
// database schema -- it opens a fresh temp database through store.Open
// (the exact same code path geniusrund itself uses) and introspects
// sqlite_master, so the generated documentation can never drift from what
// the app actually creates. Run it after any change to
// internal/store/schema.sql:
//
// go run ./cmd/dumpschema
package main
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
applog "geniusrun/backend/internal/log"
"geniusrun/backend/internal/store"
)
type schemaEntry struct {
typ string // "table" or "view"
name string
tblName string
sql string
}
func main() {
slog.SetDefault(applog.NewLogger("info", os.Stdout))
tmpDir, err := os.MkdirTemp("", "geniusrun-dumpschema")
must(err)
defer os.RemoveAll(tmpDir)
db, err := store.Open(filepath.Join(tmpDir, "schema-check.db"))
must(err)
defer db.Close()
rows, err := db.Query(`
SELECT type, name, tbl_name, sql FROM sqlite_master
WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'
ORDER BY rowid`)
must(err)
defer rows.Close()
var tables, views []schemaEntry
indexesByTable := map[string][]schemaEntry{}
for rows.Next() {
var e schemaEntry
must(rows.Scan(&e.typ, &e.name, &e.tblName, &e.sql))
switch e.typ {
case "table":
tables = append(tables, e)
case "view":
views = append(views, e)
case "index":
indexesByTable[e.tblName] = append(indexesByTable[e.tblName], e)
}
}
must(rows.Err())
var b strings.Builder
b.WriteString("# geniusrun database schema\n\n")
b.WriteString("Generated from the live schema via `go run ./cmd/dumpschema` -- do not hand-edit. ")
b.WriteString("The source of truth is `backend/internal/store/schema.sql`; regenerate this file after changing it.\n\n")
b.WriteString("## Tables\n\n")
for _, t := range tables {
fmt.Fprintf(&b, "- [`%s`](#%s)\n", t.name, t.name)
}
b.WriteString("\n")
for _, t := range tables {
fmt.Fprintf(&b, "## `%s`\n\n", t.name)
fmt.Fprintf(&b, "```sql\n%s;\n```\n\n", t.sql)
if idxs := indexesByTable[t.name]; len(idxs) > 0 {
b.WriteString("Indexes:\n\n```sql\n")
for _, idx := range idxs {
fmt.Fprintf(&b, "%s;\n", idx.sql)
}
b.WriteString("```\n\n")
}
}
if len(views) > 0 {
b.WriteString("## Views\n\n")
for _, v := range views {
fmt.Fprintf(&b, "### `%s`\n\n", v.name)
fmt.Fprintf(&b, "```sql\n%s;\n```\n\n", v.sql)
}
}
outPath := "../docs/DATABASE.md"
must(os.WriteFile(outPath, []byte(b.String()), 0644))
applog.App("main", "main").Info("wrote schema doc", "path", outPath)
}
func must(err error) {
if err != nil {
applog.App("main", "must").Error("dumpschema failed", "error", err)
os.Exit(1)
}
}