feat(B2): add tenant model + migrations 035/036 + RLS policies
Migration 035: tenants table with 'Schaeffler' default seed. Migration 036: tenant_id FK on all tables, RLS policies, backfill. New domains/tenants/ with CRUD router (admin only). All domain models extended with tenant_id FK. core/database.py: get_db_for_tenant with RLS context setter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.utils.auth import require_admin
|
||||
from app.domains.tenants.schemas import TenantCreate, TenantUpdate, TenantOut
|
||||
from app.domains.tenants import service
|
||||
|
||||
router = APIRouter(prefix="/tenants", tags=["tenants"])
|
||||
|
||||
|
||||
@router.get("/", response_model=list[TenantOut])
|
||||
async def list_tenants(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: object = Depends(require_admin),
|
||||
):
|
||||
rows = await service.list_tenants(db)
|
||||
result = []
|
||||
for row in rows:
|
||||
tenant = row["tenant"]
|
||||
out = TenantOut.model_validate(tenant)
|
||||
out.user_count = row["user_count"]
|
||||
result.append(out)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/{tenant_id}", response_model=TenantOut)
|
||||
async def get_tenant(
|
||||
tenant_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: object = Depends(require_admin),
|
||||
):
|
||||
tenant = await service.get_tenant(db, tenant_id)
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found")
|
||||
return TenantOut.model_validate(tenant)
|
||||
|
||||
|
||||
@router.post("/", response_model=TenantOut, status_code=status.HTTP_201_CREATED)
|
||||
async def create_tenant(
|
||||
body: TenantCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: object = Depends(require_admin),
|
||||
):
|
||||
tenant = await service.create_tenant(db, name=body.name, slug=body.slug, is_active=body.is_active)
|
||||
return TenantOut.model_validate(tenant)
|
||||
|
||||
|
||||
@router.put("/{tenant_id}", response_model=TenantOut)
|
||||
async def update_tenant(
|
||||
tenant_id: uuid.UUID,
|
||||
body: TenantUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: object = Depends(require_admin),
|
||||
):
|
||||
tenant = await service.update_tenant(
|
||||
db, tenant_id,
|
||||
name=body.name,
|
||||
slug=body.slug,
|
||||
is_active=body.is_active,
|
||||
)
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found")
|
||||
return TenantOut.model_validate(tenant)
|
||||
|
||||
|
||||
@router.delete("/{tenant_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_tenant(
|
||||
tenant_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: object = Depends(require_admin),
|
||||
):
|
||||
ok = await service.delete_tenant(db, tenant_id)
|
||||
if not ok:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Tenant not found or still has users assigned",
|
||||
)
|
||||
Reference in New Issue
Block a user