a0c7a80381
- Migration 0112: tenants table has no deleted_at column, remove filter - Migration 0113: guest_users table has no deleted_at column, remove filter
140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
"""Migrate guest_users to regular users with role='guest' and drop guest tables.
|
|
|
|
⚠️ Guest-System umgebaut — Guests sind jetzt reguläre User mit role=guest
|
|
|
|
This migration:
|
|
1. Creates User records for each guest (or links to existing users by email)
|
|
2. Creates UserTenant records with role='guest' and appropriate status
|
|
3. Drops guest_invitations and guest_users tables
|
|
|
|
After this migration, guests authenticate via the normal login flow and are
|
|
managed through the regular user system with role='guest' in user_tenants.
|
|
|
|
Revision ID: 0113
|
|
Revises: 0112
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from alembic import op
|
|
|
|
revision = "0113"
|
|
down_revision = "0112"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Migrate guest_users into users + user_tenants with role='guest'
|
|
op.execute("""
|
|
DO $$
|
|
DECLARE
|
|
guest_rec RECORD;
|
|
existing_user_id UUID;
|
|
new_user_id UUID;
|
|
mapped_status TEXT;
|
|
BEGIN
|
|
FOR guest_rec IN SELECT * FROM guest_users
|
|
LOOP
|
|
-- Map guest status to user_tenants status
|
|
mapped_status := CASE
|
|
WHEN guest_rec.status = 'active' THEN 'active'
|
|
WHEN guest_rec.status = 'invited' THEN 'invited'
|
|
WHEN guest_rec.status = 'expired' THEN 'disabled'
|
|
WHEN guest_rec.status = 'revoked' THEN 'disabled'
|
|
ELSE 'disabled'
|
|
END;
|
|
|
|
-- Check if a user with this email already exists
|
|
SELECT id INTO existing_user_id
|
|
FROM users
|
|
WHERE email = guest_rec.email
|
|
LIMIT 1;
|
|
|
|
IF existing_user_id IS NOT NULL THEN
|
|
-- User already exists — just create the tenant membership if missing
|
|
new_user_id := existing_user_id;
|
|
|
|
-- Check if user_tenants entry already exists for this user+tenant
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM user_tenants
|
|
WHERE user_id = new_user_id
|
|
AND tenant_id = guest_rec.tenant_id
|
|
) THEN
|
|
INSERT INTO user_tenants (user_id, tenant_id, is_default, role, status, created_at, updated_at)
|
|
VALUES (
|
|
new_user_id,
|
|
guest_rec.tenant_id,
|
|
false,
|
|
'guest',
|
|
mapped_status,
|
|
guest_rec.created_at,
|
|
guest_rec.updated_at
|
|
);
|
|
END IF;
|
|
ELSE
|
|
-- Create new user from guest record
|
|
INSERT INTO users (id, email, name, password_hash, is_active, preferences, is_system_admin, created_at, updated_at)
|
|
VALUES (
|
|
gen_random_uuid(),
|
|
guest_rec.email,
|
|
guest_rec.name,
|
|
COALESCE(guest_rec.password_hash, ''),
|
|
true,
|
|
'{}'::jsonb,
|
|
false,
|
|
guest_rec.created_at,
|
|
guest_rec.updated_at
|
|
)
|
|
RETURNING id INTO new_user_id;
|
|
|
|
-- Create user_tenants membership with guest role
|
|
INSERT INTO user_tenants (user_id, tenant_id, is_default, role, status, created_at, updated_at)
|
|
VALUES (
|
|
new_user_id,
|
|
guest_rec.tenant_id,
|
|
false,
|
|
'guest',
|
|
mapped_status,
|
|
guest_rec.created_at,
|
|
guest_rec.updated_at
|
|
);
|
|
END IF;
|
|
END LOOP;
|
|
END $$;
|
|
""")
|
|
|
|
# Drop guest tables
|
|
op.execute("DROP TABLE IF EXISTS guest_invitations CASCADE")
|
|
op.execute("DROP TABLE IF EXISTS guest_users CASCADE")
|
|
|
|
|
|
def downgrade() -> None:
|
|
# Recreate guest_users table (data is lost — this is a one-way migration)
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS guest_users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email VARCHAR(255) NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
password_hash VARCHAR(255),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
invited_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'invited',
|
|
expires_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""")
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS guest_invitations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
guest_user_id UUID NOT NULL REFERENCES guest_users(id) ON DELETE CASCADE,
|
|
token_hash VARCHAR(64) NOT NULL UNIQUE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
revoked_at TIMESTAMPTZ,
|
|
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""")
|