Implements tag/category system backend following microformats2 p-category specification. Database changes: - Migration 008: Add tags and note_tags tables - Normalized tag storage (case-insensitive lookup, display name preserved) - Indexes for performance New module: - starpunk/tags.py: Tag management functions - normalize_tag: Normalize tag strings - get_or_create_tag: Get or create tag records - add_tags_to_note: Associate tags with notes (replaces existing) - get_note_tags: Retrieve note tags (alphabetically ordered) - get_tag_by_name: Lookup tag by normalized name - get_notes_by_tag: Get all notes with specific tag - parse_tag_input: Parse comma-separated tag input Model updates: - Note.tags property (lazy-loaded, prefer pre-loading in routes) - Note.to_dict() add include_tags parameter CRUD updates: - create_note() accepts tags parameter - update_note() accepts tags parameter (None = no change, [] = remove all) Micropub integration: - Pass tags to create_note() (tags already extracted by extract_tags()) - Return tags in q=source response Per design doc: docs/design/v1.3.0/microformats-tags-design.md Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
105 lines
3.2 KiB
Markdown
105 lines
3.2 KiB
Markdown
# Migration System - Quick Reference Card
|
|
|
|
**TL;DR**: Add fresh database detection to `migrations.py` to solve chicken-and-egg problem.
|
|
|
|
## The Problem
|
|
|
|
- `SCHEMA_SQL` includes `code_verifier` column (line 60, database.py)
|
|
- Migration 001 tries to add same column
|
|
- Fresh databases fail: "column already exists"
|
|
|
|
## The Solution
|
|
|
|
**SCHEMA_SQL = Target State** (complete current schema)
|
|
- Fresh installs: Execute SCHEMA_SQL, skip migrations (already at target)
|
|
- Existing installs: Run migrations to reach target
|
|
|
|
## Code Changes Required
|
|
|
|
### 1. Add to `migrations.py` (before `run_migrations`):
|
|
|
|
```python
|
|
def is_schema_current(conn):
|
|
"""Check if database schema matches current SCHEMA_SQL"""
|
|
try:
|
|
cursor = conn.execute("PRAGMA table_info(auth_state)")
|
|
columns = [row[1] for row in cursor.fetchall()]
|
|
return 'code_verifier' in columns
|
|
except sqlite3.OperationalError:
|
|
return False
|
|
```
|
|
|
|
### 2. Modify `run_migrations()` in `migrations.py`:
|
|
|
|
After `create_migrations_table(conn)`, before applying migrations, add:
|
|
|
|
```python
|
|
# Check if this is a fresh database
|
|
cursor = conn.execute("SELECT COUNT(*) FROM schema_migrations")
|
|
migration_count = cursor.fetchone()[0]
|
|
|
|
# Discover migration files
|
|
migration_files = discover_migration_files(migrations_dir)
|
|
|
|
# Fresh database detection
|
|
if migration_count == 0 and is_schema_current(conn):
|
|
# Mark all migrations as applied (schema already current)
|
|
for migration_name, _ in migration_files:
|
|
conn.execute(
|
|
"INSERT INTO schema_migrations (migration_name) VALUES (?)",
|
|
(migration_name,)
|
|
)
|
|
conn.commit()
|
|
logger.info(f"Fresh database: marked {len(migration_files)} migrations as applied")
|
|
return
|
|
```
|
|
|
|
### 3. Optional Helpers (add to `migrations.py` for future use):
|
|
|
|
```python
|
|
def table_exists(conn, table_name):
|
|
cursor = conn.execute(
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
(table_name,)
|
|
)
|
|
return cursor.fetchone() is not None
|
|
|
|
def column_exists(conn, table_name, column_name):
|
|
try:
|
|
cursor = conn.execute(f"PRAGMA table_info({table_name})")
|
|
columns = [row[1] for row in cursor.fetchall()]
|
|
return column_name in columns
|
|
except sqlite3.OperationalError:
|
|
return False
|
|
```
|
|
|
|
## Test It
|
|
|
|
```bash
|
|
# Test 1: Fresh database
|
|
rm data/starpunk.db && uv run flask --app app.py run
|
|
# Expected: "Fresh database: marked 1 migrations as applied"
|
|
|
|
# Test 2: Legacy database (before PKCE)
|
|
# Create old schema, run app
|
|
# Expected: "Applied migration: 001_add_code_verifier..."
|
|
```
|
|
|
|
## All Other Questions Answered
|
|
|
|
- **Q2**: schema_migrations only in migrations.py ✓ (already correct)
|
|
- **Q3**: Accept non-idempotent SQL, rely on tracking ✓ (already works)
|
|
- **Q4**: Flexible filename validation ✓ (already implemented)
|
|
- **Q5**: Automatic transition via Q1 solution ✓
|
|
- **Q6**: Helpers provided for advanced use ✓ (see above)
|
|
- **Q7**: SCHEMA_SQL is target state ✓ (no changes needed)
|
|
|
|
## Full Details
|
|
|
|
See: `/home/phil/Projects/starpunk/docs/reports/2025-11-19-migration-system-implementation-guidance.md`
|
|
|
|
## Architecture Reference
|
|
|
|
See: `/home/phil/Projects/starpunk/docs/decisions/ADR-020-automatic-database-migrations.md`
|
|
(New section: "Developer Questions & Architectural Responses")
|