store: collapse migrations into a single schema.sql, drop legacy-owner path
Pre-production app, no need to preserve incremental migration history: replace the 26 migration files with one current-state schema.sql (the schema.sql comments are now the living documentation), simplify db.go to apply it once instead of tracking/rebuilding through schema_migrations, and make user_id NOT NULL everywhere now that there's no staged migration to accommodate a nullable backfill window. This removes the reason ClaimLegacyOwner/GENIUSRUN_LEGACY_OWNER_OIDC_SUB existed (binding a pre-existing singleton-schema database to one account across a staged migration), so that whole path is gone too -- the existing dev database was wiped and reseeded fresh under the new schema. Add cmd/dumpschema, which regenerates docs/DATABASE.md straight from the live schema (via store.Open + sqlite_master introspection) so the database documentation can never drift out of sync with reality. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
101
backend/cmd/dumpschema/main.go
Normal file
101
backend/cmd/dumpschema/main.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"geniusrun/backend/internal/store"
|
||||
)
|
||||
|
||||
type schemaEntry struct {
|
||||
typ string // "table" or "view"
|
||||
name string
|
||||
tblName string
|
||||
sql string
|
||||
}
|
||||
|
||||
func main() {
|
||||
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))
|
||||
log.Printf("wrote %s", outPath)
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user