fix: resolve circular FK between users and accounts on registration
Account.owner_id and User.account_id are both NOT NULL, creating a circular dependency that prevents inserting either row first. Fix by: 1. Making owner_id nullable (set immediately after user creation) 2. Creating Account before User, then setting owner_id after flush 3. Removing NOT NULL enforcement on owner_id in migration 020 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,8 +81,7 @@ def upgrade() -> None:
|
||||
['account_id'], ['id'], ondelete='CASCADE'
|
||||
)
|
||||
|
||||
# 4. Accounts: enforce owner_id NOT NULL + FK
|
||||
op.alter_column('accounts', 'owner_id', nullable=False)
|
||||
# 4. Accounts: add owner FK (owner_id stays nullable due to circular FK with users)
|
||||
op.create_foreign_key(
|
||||
'fk_accounts_owner_id', 'accounts', 'users',
|
||||
['owner_id'], ['id'], ondelete='RESTRICT'
|
||||
|
||||
@@ -134,35 +134,47 @@ async def register(
|
||||
detail="Email already registered"
|
||||
)
|
||||
|
||||
# Create new user
|
||||
new_user = User(
|
||||
email=user_data.email,
|
||||
password_hash=get_password_hash(user_data.password),
|
||||
name=user_data.name,
|
||||
role="engineer",
|
||||
invite_code_id=invite_code_record.id if invite_code_record else None
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush() # Get user ID before creating account
|
||||
|
||||
if account_invite_record:
|
||||
# Join existing account via account invite
|
||||
new_user.account_id = account_invite_record.account_id
|
||||
new_user.account_role = account_invite_record.role
|
||||
new_user = User(
|
||||
email=user_data.email,
|
||||
password_hash=get_password_hash(user_data.password),
|
||||
name=user_data.name,
|
||||
role="engineer",
|
||||
invite_code_id=invite_code_record.id if invite_code_record else None,
|
||||
account_id=account_invite_record.account_id,
|
||||
account_role=account_invite_record.role,
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
|
||||
# Mark account invite as used
|
||||
account_invite_record.accepted_by_id = new_user.id
|
||||
account_invite_record.used_at = datetime.now(timezone.utc)
|
||||
else:
|
||||
# Create personal Account + free Subscription
|
||||
# Create personal Account first (user needs account_id for NOT NULL constraint)
|
||||
new_account = Account(
|
||||
name=f"{user_data.name}'s Account",
|
||||
display_code=_generate_display_code(),
|
||||
owner_id=new_user.id,
|
||||
)
|
||||
db.add(new_account)
|
||||
await db.flush() # Get account ID
|
||||
|
||||
new_user = User(
|
||||
email=user_data.email,
|
||||
password_hash=get_password_hash(user_data.password),
|
||||
name=user_data.name,
|
||||
role="engineer",
|
||||
invite_code_id=invite_code_record.id if invite_code_record else None,
|
||||
account_id=new_account.id,
|
||||
account_role="owner",
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush() # Get user ID
|
||||
|
||||
# Now set account owner and create subscription
|
||||
new_account.owner_id = new_user.id
|
||||
|
||||
new_subscription = Subscription(
|
||||
account_id=new_account.id,
|
||||
plan="free",
|
||||
@@ -170,9 +182,6 @@ async def register(
|
||||
)
|
||||
db.add(new_subscription)
|
||||
|
||||
new_user.account_id = new_account.id
|
||||
new_user.account_role = "owner"
|
||||
|
||||
# Mark platform invite code as used
|
||||
if invite_code_record:
|
||||
invite_code_record.used_by_id = new_user.id
|
||||
|
||||
@@ -22,7 +22,7 @@ class Account(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
display_code: Mapped[str] = mapped_column(String(8), unique=True, nullable=False)
|
||||
owner_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="RESTRICT"), nullable=False)
|
||||
owner_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="RESTRICT"), nullable=True)
|
||||
stripe_customer_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
||||
Reference in New Issue
Block a user