package db import ( "database/sql" "fmt" ) var migrations = []struct { name string sql string }{ { name: "001_initial_schema", sql: ` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS webauthn_credentials ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, credential_id BLOB NOT NULL UNIQUE, public_key BLOB NOT NULL, name TEXT NOT NULL, sign_count INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS notebooks ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, remote_id INTEGER NOT NULL, title TEXT NOT NULL, page_size TEXT NOT NULL, synced_at INTEGER NOT NULL, UNIQUE(user_id, remote_id) ); CREATE TABLE IF NOT EXISTS pages ( id INTEGER PRIMARY KEY AUTOINCREMENT, notebook_id INTEGER NOT NULL REFERENCES notebooks(id) ON DELETE CASCADE, remote_id INTEGER NOT NULL, page_number INTEGER NOT NULL, UNIQUE(notebook_id, remote_id) ); CREATE TABLE IF NOT EXISTS strokes ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE, pen_size REAL NOT NULL, color INTEGER NOT NULL, style TEXT NOT NULL DEFAULT 'plain', point_data BLOB NOT NULL, stroke_order INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS share_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, notebook_id INTEGER NOT NULL REFERENCES notebooks(id) ON DELETE CASCADE, token TEXT NOT NULL UNIQUE, expires_at INTEGER, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS schema_migrations ( name TEXT PRIMARY KEY, applied_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_notebooks_user ON notebooks(user_id); CREATE INDEX IF NOT EXISTS idx_pages_notebook ON pages(notebook_id); CREATE INDEX IF NOT EXISTS idx_strokes_page ON strokes(page_id); CREATE INDEX IF NOT EXISTS idx_share_links_token ON share_links(token); CREATE INDEX IF NOT EXISTS idx_webauthn_user ON webauthn_credentials(user_id); `, }, } func Migrate(database *sql.DB) error { // Ensure schema_migrations table exists _, err := database.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations ( name TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)`) if err != nil { return fmt.Errorf("create schema_migrations: %w", err) } for _, m := range migrations { var count int err := database.QueryRow("SELECT COUNT(*) FROM schema_migrations WHERE name = ?", m.name).Scan(&count) if err != nil { return fmt.Errorf("check migration %s: %w", m.name, err) } if count > 0 { continue } if _, err := database.Exec(m.sql); err != nil { return fmt.Errorf("apply migration %s: %w", m.name, err) } if _, err := database.Exec( "INSERT INTO schema_migrations (name, applied_at) VALUES (?, strftime('%s','now'))", m.name, ); err != nil { return fmt.Errorf("record migration %s: %w", m.name, err) } } return nil }