An admin can now queue up an email address in advance (POST/GET/DELETE /system/pending-provisioning) with a source user and an optional admin flag. The moment that email actually verifies a real account - not raw registration, which proves nothing about ownership - it gets a deep copy of the source user's per-user API keys and every company they own (company profile, aliases, competitors, monitor config, sources, source documents, monitoring runs, reports, snapshots, detected changes, and enrichment - not just the company row), plus an email notification destination for its own address linked to the copied companies. The clone logic (_copy_row/_clone_company in provisioning_service.py) is generic over every table it touches via column introspection, so it doesn't need hand-maintained field lists per model. Co-Authored-By: Claude Sonnet 5 <[email protected]>
46 lines
1.8 KiB
Python
46 lines
1.8 KiB
Python
"""add pending_provisionings table
|
|
|
|
Revision ID: 2c2a563a5edb
|
|
Revises: 9e6c80c11da7
|
|
Create Date: 2026-08-06 22:22:22.348339
|
|
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision: str = '2c2a563a5edb'
|
|
down_revision: str | None = '9e6c80c11da7'
|
|
branch_labels: Sequence[str] | str | None = None
|
|
depends_on: Sequence[str] | str | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Autogenerate also proposed adding a redundant UNIQUE constraint on the
|
|
# `id` column of every other existing table (a cosmetic artifact of how
|
|
# this project's earlier migrations declared unnamed unique constraints,
|
|
# unrelated to this change) - stripped out, keeping only the new table.
|
|
op.create_table('pending_provisionings',
|
|
sa.Column('email', sa.String(length=320), nullable=False),
|
|
sa.Column('source_user_id', sa.Uuid(), nullable=False),
|
|
sa.Column('make_admin', sa.Boolean(), nullable=False),
|
|
sa.Column('id', sa.Uuid(), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.ForeignKeyConstraint(['source_user_id'], ['users.id'], ondelete='CASCADE'),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_pending_provisionings_email'), 'pending_provisionings', ['email'], unique=True)
|
|
op.create_index(op.f('ix_pending_provisionings_source_user_id'), 'pending_provisionings', ['source_user_id'], unique=False)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index(op.f('ix_pending_provisionings_source_user_id'), table_name='pending_provisionings')
|
|
op.drop_index(op.f('ix_pending_provisionings_email'), table_name='pending_provisionings')
|
|
op.drop_table('pending_provisionings')
|