mitai-jinkendo/backend/apply_v9c_migration.py
Lars 2f302b26af
All checks were successful
Deploy Development / deploy (push) Successful in 53s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 12s
feat: add v9c subscription system database schema
Phase 1: Database Migration Complete

Created migration infrastructure:
- backend/migrations/v9c_subscription_system.sql (11 new tables)
- backend/apply_v9c_migration.py (auto-migration runner)
- Updated main.py startup event to apply migration

New tables (Feature-Registry Pattern):
1. app_settings - Global configuration
2. tiers - Subscription tiers (free/basic/premium/selfhosted)
3. features - Feature registry (11 limitable features)
4. tier_limits - Tier x Feature matrix (44 initial limits)
5. user_feature_restrictions - Individual user overrides
6. user_feature_usage - Usage tracking with reset periods
7. coupons - Coupon management (single-use, period, Wellpass)
8. coupon_redemptions - Redemption history
9. access_grants - Time-limited access with pause/resume logic
10. user_activity_log - Activity tracking (JSONB details)
11. user_stats - Aggregated statistics

Extended profiles table:
- tier, trial_ends_at, email_verified, email_verify_token
- invited_by, invitation_token

Initial data inserted:
- 4 tiers (free/basic/premium/selfhosted)
- 11 features (weight, circumference, caliper, nutrition, activity, photos, ai_calls, ai_pipeline, export_*)
- 44 tier_limits (complete Tier x Feature matrix)
- App settings (trial duration, self-registration config)

Migration auto-runs on container startup (similar to SQLite→PostgreSQL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 12:42:43 +01:00

118 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""
Apply v9c Subscription System Migration
This script checks if v9c migration is needed and applies it.
Run automatically on container startup via main.py startup event.
"""
import os
import psycopg2
from psycopg2.extras import RealDictCursor
def get_db_connection():
"""Get PostgreSQL connection."""
return psycopg2.connect(
host=os.getenv("DB_HOST", "postgres"),
port=int(os.getenv("DB_PORT", 5432)),
database=os.getenv("DB_NAME", "mitai_prod"),
user=os.getenv("DB_USER", "mitai_prod"),
password=os.getenv("DB_PASSWORD", ""),
cursor_factory=RealDictCursor
)
def migration_needed(conn):
"""Check if v9c migration is needed."""
cur = conn.cursor()
# Check if tiers table exists
cur.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'tiers'
)
""")
tiers_exists = cur.fetchone()['exists']
# Check if features table exists
cur.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'features'
)
""")
features_exists = cur.fetchone()['exists']
cur.close()
# Migration needed if either table is missing
return not (tiers_exists and features_exists)
def apply_migration():
"""Apply v9c migration if needed."""
print("[v9c Migration] Checking if migration is needed...")
try:
conn = get_db_connection()
if not migration_needed(conn):
print("[v9c Migration] Already applied, skipping.")
conn.close()
return
print("[v9c Migration] Applying subscription system migration...")
# Read migration SQL
migration_path = os.path.join(
os.path.dirname(__file__),
"migrations",
"v9c_subscription_system.sql"
)
with open(migration_path, 'r', encoding='utf-8') as f:
migration_sql = f.read()
# Execute migration
cur = conn.cursor()
cur.execute(migration_sql)
conn.commit()
cur.close()
conn.close()
print("[v9c Migration] ✅ Migration completed successfully!")
# Verify tables created
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('tiers', 'features', 'tier_limits', 'access_grants', 'coupons')
ORDER BY table_name
""")
tables = [r['table_name'] for r in cur.fetchall()]
print(f"[v9c Migration] Created tables: {', '.join(tables)}")
# Verify initial data
cur.execute("SELECT COUNT(*) as count FROM tiers")
tier_count = cur.fetchone()['count']
cur.execute("SELECT COUNT(*) as count FROM features")
feature_count = cur.fetchone()['count']
cur.execute("SELECT COUNT(*) as count FROM tier_limits")
limit_count = cur.fetchone()['count']
print(f"[v9c Migration] Initial data: {tier_count} tiers, {feature_count} features, {limit_count} tier limits")
cur.close()
conn.close()
except Exception as e:
print(f"[v9c Migration] ❌ Error: {e}")
raise
if __name__ == "__main__":
apply_migration()