# Initial Schema SQL Implementation Guide ## Overview This guide provides step-by-step instructions for implementing the INITIAL_SCHEMA_SQL constant and updating the database initialization system as specified in ADR-032. **Priority**: CRITICAL for v1.1.0 **Estimated Time**: 4-6 hours **Risk Level**: Low (backward compatible) ## Pre-Implementation Checklist - [ ] Read ADR-031 (Database Migration System Redesign) - [ ] Read ADR-032 (Initial Schema SQL Implementation) - [ ] Review current migrations in `/migrations/` directory - [ ] Backup any test databases - [ ] Ensure test environment is ready ## Implementation Steps ### Step 1: Add INITIAL_SCHEMA_SQL Constant **File**: `/home/phil/Projects/starpunk/starpunk/database.py` **Action**: Add the following constant ABOVE the current SCHEMA_SQL: ```python # Database schema - V0.1.0 baseline (see ADR-032) # This represents the initial database structure from commit a68fd57 # All schema evolution happens through migrations from this baseline INITIAL_SCHEMA_SQL = """ -- Notes metadata (content is in files) CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT UNIQUE NOT NULL, file_path TEXT UNIQUE NOT NULL, published BOOLEAN DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, content_hash TEXT ); CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at DESC); CREATE INDEX IF NOT EXISTS idx_notes_published ON notes(published); CREATE INDEX IF NOT EXISTS idx_notes_slug ON notes(slug); CREATE INDEX IF NOT EXISTS idx_notes_deleted_at ON notes(deleted_at); -- Authentication sessions (IndieLogin) CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_token TEXT UNIQUE NOT NULL, me TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, last_used_at TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token); CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at); -- Micropub access tokens (original insecure version) CREATE TABLE IF NOT EXISTS tokens ( token TEXT PRIMARY KEY, me TEXT NOT NULL, client_id TEXT, scope TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_tokens_me ON tokens(me); -- CSRF state tokens (for IndieAuth flow) CREATE TABLE IF NOT EXISTS auth_state ( state TEXT PRIMARY KEY, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL ); CREATE INDEX IF NOT EXISTS idx_auth_state_expires ON auth_state(expires_at); """ ``` ### Step 2: Rename Current SCHEMA_SQL **File**: `/home/phil/Projects/starpunk/starpunk/database.py` **Action**: Rename the existing SCHEMA_SQL constant and add documentation: ```python # Current database schema - FOR DOCUMENTATION ONLY # This shows the current complete schema after all migrations # NOT used for database initialization - see INITIAL_SCHEMA_SQL # Updated by migrations 001 and 002 CURRENT_SCHEMA_SQL = """ [existing SCHEMA_SQL content] """ ``` ### Step 3: Add Helper Function **File**: `/home/phil/Projects/starpunk/starpunk/database.py` **Action**: Add this function before init_db(): ```python def database_exists_with_tables(db_path): """ Check if database exists and has tables Args: db_path: Path to SQLite database file Returns: bool: True if database exists with at least one table """ import os # Check if file exists if not os.path.exists(db_path): return False # Check if it has tables try: conn = sqlite3.connect(db_path) cursor = conn.execute( "SELECT COUNT(*) FROM sqlite_master WHERE type='table'" ) table_count = cursor.fetchone()[0] conn.close() return table_count > 0 except Exception: return False ``` ### Step 4: Update init_db() Function **File**: `/home/phil/Projects/starpunk/starpunk/database.py` **Action**: Replace the init_db() function with: ```python def init_db(app=None): """ Initialize database schema and run migrations For fresh databases: 1. Creates v0.1.0 baseline schema (INITIAL_SCHEMA_SQL) 2. Runs all migrations to bring to current version For existing databases: 1. Skips schema creation (tables already exist) 2. Runs only pending migrations Args: app: Flask application instance (optional, for config access) """ if app: db_path = app.config["DATABASE_PATH"] logger = app.logger else: # Fallback to default path db_path = Path("./data/starpunk.db") logger = logging.getLogger(__name__) # Ensure parent directory exists db_path.parent.mkdir(parents=True, exist_ok=True) # Check if this is an existing database if database_exists_with_tables(db_path): # Existing database - skip schema creation, only run migrations logger.info(f"Existing database found: {db_path}") logger.info("Running pending migrations...") else: # Fresh database - create initial v0.1.0 schema logger.info(f"Creating new database: {db_path}") conn = sqlite3.connect(db_path) try: # Create v0.1.0 baseline schema conn.executescript(INITIAL_SCHEMA_SQL) conn.commit() logger.info("Created initial v0.1.0 database schema") except Exception as e: logger.error(f"Failed to create initial schema: {e}") raise finally: conn.close() # Run migrations (for both fresh and existing databases) # This will apply ALL migrations for fresh databases, # or only pending migrations for existing databases from starpunk.migrations import run_migrations try: run_migrations(db_path, logger) except Exception as e: logger.error(f"Migration failed: {e}") raise ``` ### Step 5: Update Tests **File**: `/home/phil/Projects/starpunk/tests/test_migrations.py` **Add these test cases**: ```python def test_fresh_database_initialization(tmp_path): """Test that fresh database gets initial schema then migrations""" db_path = tmp_path / "test.db" # Initialize fresh database init_db_with_path(db_path) # Verify initial tables exist conn = sqlite3.connect(db_path) cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name" ) tables = [row[0] for row in cursor.fetchall()] # Should have all tables including migration tracking assert "notes" in tables assert "sessions" in tables assert "tokens" in tables assert "auth_state" in tables assert "schema_migrations" in tables assert "authorization_codes" in tables # Added by migration 002 # Verify migrations were applied cursor = conn.execute("SELECT COUNT(*) FROM schema_migrations") migration_count = cursor.fetchone()[0] assert migration_count >= 2 # At least migrations 001 and 002 conn.close() def test_existing_database_upgrade(tmp_path): """Test that existing database only runs pending migrations""" db_path = tmp_path / "test.db" # Create a database with v0.1.0 schema manually conn = sqlite3.connect(db_path) conn.executescript(INITIAL_SCHEMA_SQL) conn.commit() conn.close() # Run init_db on existing database init_db_with_path(db_path) # Verify migrations were applied conn = sqlite3.connect(db_path) # Check that migration 001 was applied (code_verifier column) cursor = conn.execute("PRAGMA table_info(auth_state)") columns = [row[1] for row in cursor.fetchall()] assert "code_verifier" in columns # Check that migration 002 was applied (authorization_codes table) cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='authorization_codes'" ) assert cursor.fetchone() is not None conn.close() ``` ### Step 6: Manual Testing Procedure 1. **Test Fresh Database**: ```bash # Backup existing database mv data/starpunk.db data/starpunk.db.backup # Start application (will create fresh database) uv run python app.py # Verify application starts without errors # Check logs for "Created initial v0.1.0 database schema" # Check logs for "Applied migration: 001_add_code_verifier_to_auth_state.sql" # Check logs for "Applied migration: 002_secure_tokens_and_authorization_codes.sql" ``` 2. **Test Existing Database**: ```bash # Restore backup cp data/starpunk.db.backup data/starpunk.db # Start application uv run python app.py # Verify application starts without errors # Check logs for "Existing database found" # Check logs for migration status ``` 3. **Test Database Queries**: ```bash sqlite3 data/starpunk.db # Check tables .tables # Check schema_migrations SELECT * FROM schema_migrations; # Verify authorization_codes table exists .schema authorization_codes # Verify tokens table has token_hash column .schema tokens ``` ### Step 7: Update Documentation **File**: `/home/phil/Projects/starpunk/docs/architecture/database.md` **Add section**: ```markdown ## Schema Evolution Strategy StarPunk uses a baseline + migrations approach for schema management: 1. **INITIAL_SCHEMA_SQL**: Represents the v0.1.0 baseline schema 2. **Migrations**: All schema changes applied sequentially 3. **CURRENT_SCHEMA_SQL**: Documentation of current complete schema This ensures: - Predictable upgrade paths from any version - Clear schema history through migrations - Testable database evolution ``` ## Validation Checklist After implementation, verify: - [ ] Fresh database initialization works - [ ] Existing database upgrade works - [ ] No duplicate index/table errors - [ ] All tests pass - [ ] Application starts normally - [ ] Can create/read/update notes - [ ] Authentication still works - [ ] Micropub endpoint functional ## Troubleshooting ### Issue: "table already exists" error **Solution**: Check that database_exists_with_tables() is working correctly ### Issue: "no such column" error **Solution**: Verify INITIAL_SCHEMA_SQL matches v0.1.0 exactly ### Issue: Migrations not running **Solution**: Check migrations/ directory path and file permissions ### Issue: Tests failing **Solution**: Ensure test database is properly isolated from production ## Rollback Procedure If issues occur: 1. Restore database backup 2. Revert code changes 3. Document issue in ADR-032 4. Re-plan implementation ## Post-Implementation 1. Update CHANGELOG.md 2. Update version number to 1.1.0-rc.1 3. Create release notes 4. Test Docker container with new schema 5. Document any discovered edge cases ## Contact for Questions If you encounter issues not covered in this guide: 1. Review ADR-031 and ADR-032 2. Check existing migration test cases 3. Review git history for database.py evolution 4. Document any new findings in /docs/reports/ --- *Created: 2025-11-24* *For: StarPunk v1.1.0* *Priority: CRITICAL*