feat: add Alembic database migration system
Implements Alembic for managing token storage database schema versions. Migrations run automatically on startup with full backward compatibility. **Changes:** - Add Alembic dependency (1.14.0+) and SQLAlchemy (auto-installed) - Create migration infrastructure in alembic/ directory - Add initial migration (001) capturing current schema - Modify RefreshTokenStorage.initialize() to run migrations via anyio - Add CLI commands: db upgrade, current, history, downgrade, migrate - Add comprehensive migration documentation **Backward Compatibility:** - Pre-Alembic databases automatically stamped with revision 001 - No schema changes for existing databases - Automatic upgrade on first startup after update **Migration Strategy:** Three scenarios handled: 1. New database → Run migrations from scratch 2. Pre-Alembic database → Stamp with 001 (no changes) 3. Alembic-managed → Upgrade to latest **Architecture:** - Uses anyio.to_thread.run_sync() for structured concurrency - Alembic env.py runs with anyio.run() in worker thread - SQLite-friendly migration patterns documented - No ThreadPoolExecutor needed (anyio handles it) **CLI Usage:** ```bash nextcloud-mcp-server db upgrade # Upgrade to latest nextcloud-mcp-server db current # Show version nextcloud-mcp-server db history # View changelog nextcloud-mcp-server db downgrade # Rollback (with confirmation) nextcloud-mcp-server db migrate "description" # Create migration ``` **Testing:** - All 13 webhook storage tests pass - New/pre-Alembic database scenarios validated - anyio integration tested 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
# Alembic configuration file for nextcloud-mcp-server
|
||||
|
||||
[alembic]
|
||||
# Path to migration scripts
|
||||
script_location = alembic
|
||||
|
||||
# Template used to generate migration file names
|
||||
# Default: %%(rev)s_%%(slug)s
|
||||
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
|
||||
|
||||
# Timezone for migration timestamps
|
||||
# Default: utc
|
||||
timezone = utc
|
||||
|
||||
# Max length of characters to apply to the "slug" field
|
||||
# Default: 40
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# Set to 'true' to run the environment during the 'revision' command
|
||||
# Default: false
|
||||
# revision_environment = false
|
||||
|
||||
# Set to 'true' to allow .pyc and .pyo files without a source .py file
|
||||
# Default: false
|
||||
# sourceless = false
|
||||
|
||||
# Version location specification
|
||||
# Supports single or multiple directories
|
||||
version_locations = alembic/versions
|
||||
|
||||
# Path separator for version locations (required to suppress deprecation warning)
|
||||
# Use os (for cross-platform compatibility)
|
||||
path_separator = os
|
||||
|
||||
# Set to 'true' to search source files recursively in each "version_locations" directory
|
||||
# Default: false
|
||||
# recursive_version_locations = false
|
||||
|
||||
# Output encoding used when revision files are written
|
||||
# Default: utf-8
|
||||
# output_encoding = utf-8
|
||||
|
||||
# Database URL - can be overridden by:
|
||||
# 1. Passing -x database_url=... to alembic commands
|
||||
# 2. Setting in environment via get_database_url() in env.py
|
||||
# Default: sqlite:///app/data/tokens.db
|
||||
sqlalchemy.url = sqlite+aiosqlite:////app/data/tokens.db
|
||||
|
||||
[post_write_hooks]
|
||||
# Post-write hooks allow you to run scripts after generating migration files
|
||||
# Example: format migrations with ruff
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = format REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
Reference in New Issue
Block a user