- admin_email bzw. reseller email optional; ohne E-Mail wird interne Login- Kennung (<name>@<slug>.local) + einmaliges Temp-Passwort erzeugt, Account sofort aktiv, kein Mailversand - TenantOut/ResellerOut: initial_password (einmalig) ergänzt - LoginRequest.email: str statt EmailStr (Kennung muss kein zustellbares Postfach sein; .local-Domains sind sonst nicht einloggbar) - Frontend: E-Mail-Felder optional, CredDialog zeigt Login + Temp-Passwort einmalig - Test: Anlage ohne E-Mail + Login mit generierten Zugangsdaten; 173/173 grün Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
"""Reseller-Selbstverwaltung: ein Reseller verwaltet ausschließlich die von ihm
|
||
angelegten Firmen (Firmen-Ebene, keine personenbezogenen Daten).
|
||
|
||
RLS (app.reseller_id) erzwingt, dass nur eigene Firmen sichtbar/änderbar sind –
|
||
ein Zugriff auf eine fremde Firmen-ID läuft ins Leere (404).
|
||
"""
|
||
from uuid import UUID
|
||
|
||
from fastapi import APIRouter, Depends
|
||
from fastapi import HTTPException
|
||
from sqlalchemy import text
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.core.database import get_db
|
||
from app.core.dependencies import require_role
|
||
from app.models.audit_log import AuditLog
|
||
from app.models.company import Company
|
||
from app.models.user import User, UserRole
|
||
from app.schemas.reseller import TenantCreate, TenantOut, TenantUpdate
|
||
from app.services.tenant_service import tenant_service
|
||
|
||
router = APIRouter(prefix="/reseller", tags=["Reseller"])
|
||
|
||
_reseller_roles = (UserRole.RESELLER,)
|
||
|
||
|
||
@router.get("/companies", response_model=list[TenantOut])
|
||
async def list_my_companies(
|
||
current_user: User = require_role(*_reseller_roles),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
return await tenant_service.list_tenants(db)
|
||
|
||
|
||
@router.post("/companies", response_model=TenantOut, status_code=201)
|
||
async def create_company(
|
||
data: TenantCreate,
|
||
current_user: User = require_role(*_reseller_roles),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
company, admin, initial_password = await tenant_service.create_tenant(
|
||
data, actor=current_user, db=db, reseller_id=current_user.id,
|
||
)
|
||
# Antwort aus der frisch erstellten Firma; bei Einladung Account inaktiv,
|
||
# bei Temp-Passwort sofort aktiv.
|
||
return TenantOut(
|
||
id=company.id, name=company.name, slug=company.slug, plan=company.plan,
|
||
is_active=company.is_active, country=company.country, reseller_id=company.reseller_id,
|
||
reseller_name=f"{current_user.first_name} {current_user.last_name}".strip(),
|
||
user_count=1, active_user_count=1 if initial_password else 0,
|
||
admin_email=admin.email, initial_password=initial_password,
|
||
)
|
||
|
||
|
||
@router.patch("/companies/{company_id}", response_model=TenantOut)
|
||
async def update_company(
|
||
company_id: UUID,
|
||
data: TenantUpdate,
|
||
current_user: User = require_role(*_reseller_roles),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
company = await db.get(Company, company_id)
|
||
# RLS gibt nur eigene Firmen frei → fremde/unbekannte ID = nicht sichtbar
|
||
if company is None or company.reseller_id != current_user.id:
|
||
raise HTTPException(status_code=404, detail="Firma nicht gefunden.")
|
||
|
||
changes = data.model_dump(exclude_none=True)
|
||
# Eigentümerschaft ist geprüft → Schreiben (inkl. AuditLog) unter kurzem Bypass,
|
||
# da der Reseller keine app.company_id besitzt (audit_logs ist company-gefenced).
|
||
await db.execute(text("SET LOCAL app.bypass_rls = 'on'"))
|
||
try:
|
||
for field, value in changes.items():
|
||
setattr(company, field, value)
|
||
if changes:
|
||
db.add(AuditLog(
|
||
company_id=company.id, user_id=current_user.id,
|
||
action="tenant_updated", entity_type="company", entity_id=company.id,
|
||
new_value=changes,
|
||
))
|
||
await db.flush()
|
||
finally:
|
||
await db.execute(text("SET LOCAL app.bypass_rls = 'off'"))
|
||
|
||
tenants = {t.id: t for t in await tenant_service.list_tenants(db)}
|
||
return tenants[company.id]
|