Files
space-booking/backend/migrate_to_multi_property.py
Claude Agent e21cf03a16 feat: add multi-tenant system with properties, organizations, and public booking
Implement complete multi-property architecture:
- Properties (groups of spaces) with public/private visibility
- Property managers (many-to-many) with role-based permissions
- Organizations with member management
- Anonymous/guest booking support via public API (/api/public/*)
- Property-scoped spaces, bookings, and settings
- Frontend: property selector, organization management, public booking views
- Migration script and updated seed data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:17:21 +00:00

107 lines
4.2 KiB
Python

"""Migration script to add multi-property support to existing database."""
import sys
import os
# Add backend to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from sqlalchemy import inspect, text
from app.db.session import Base, SessionLocal, engine
from app.models import (
Organization, OrganizationMember, Property, PropertyAccess,
PropertyManager, PropertySettings, User, Space,
)
def migrate():
"""Run migration to add multi-property tables and data."""
db = SessionLocal()
inspector = inspect(engine)
existing_tables = inspector.get_table_names()
print("Starting multi-property migration...")
# Step 1: Create all new tables
print("1. Creating new tables...")
Base.metadata.create_all(bind=engine)
print(" Tables created successfully.")
# Step 2: Add property_id column to spaces if not exists
space_columns = [col["name"] for col in inspector.get_columns("spaces")]
if "property_id" not in space_columns:
print("2. Adding property_id column to spaces...")
with engine.connect() as conn:
conn.execute(text("ALTER TABLE spaces ADD COLUMN property_id INTEGER REFERENCES properties(id)"))
conn.commit()
print(" Column added.")
else:
print("2. property_id column already exists in spaces.")
# Step 3: Add guest columns to bookings if not exists
booking_columns = [col["name"] for col in inspector.get_columns("bookings")]
with engine.connect() as conn:
if "guest_name" not in booking_columns:
print("3. Adding guest columns to bookings...")
conn.execute(text("ALTER TABLE bookings ADD COLUMN guest_name VARCHAR"))
conn.execute(text("ALTER TABLE bookings ADD COLUMN guest_email VARCHAR"))
conn.execute(text("ALTER TABLE bookings ADD COLUMN guest_organization VARCHAR"))
conn.execute(text("ALTER TABLE bookings ADD COLUMN is_anonymous BOOLEAN DEFAULT 0 NOT NULL"))
conn.commit()
print(" Guest columns added.")
else:
print("3. Guest columns already exist in bookings.")
# Step 4: Create "Default Property"
print("4. Creating Default Property...")
existing_default = db.query(Property).filter(Property.name == "Default Property").first()
if not existing_default:
default_prop = Property(
name="Default Property",
description="Default property for migrated spaces",
is_public=True,
is_active=True,
)
db.add(default_prop)
db.flush()
# Step 5: Migrate existing spaces to Default Property
print("5. Migrating existing spaces to Default Property...")
spaces_without_property = db.query(Space).filter(Space.property_id == None).all() # noqa: E711
for space in spaces_without_property:
space.property_id = default_prop.id
db.flush()
print(f" Migrated {len(spaces_without_property)} spaces.")
# Step 6: Rename admin users to superadmin
print("6. Updating admin roles to superadmin...")
admin_users = db.query(User).filter(User.role == "admin").all()
for u in admin_users:
u.role = "superadmin"
db.flush()
print(f" Updated {len(admin_users)} users.")
# Step 7: Create PropertyManager entries for superadmins
print("7. Creating PropertyManager entries for superadmins...")
superadmins = db.query(User).filter(User.role == "superadmin").all()
for sa in superadmins:
existing_pm = db.query(PropertyManager).filter(
PropertyManager.property_id == default_prop.id,
PropertyManager.user_id == sa.id,
).first()
if not existing_pm:
db.add(PropertyManager(property_id=default_prop.id, user_id=sa.id))
db.flush()
print(f" Created entries for {len(superadmins)} superadmins.")
db.commit()
print("\nMigration completed successfully!")
else:
print(" Default Property already exists. Skipping data migration.")
print("\nMigration already applied.")
db.close()
if __name__ == "__main__":
migrate()