187 lines
7.3 KiB
Python
187 lines
7.3 KiB
Python
import uuid
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, status, HTTPException, Query, Request
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.config.database import get_db
|
|
from app.controllers.auth.tenant_controller import TenantController
|
|
from app.schemas.auth.tenant_schema import TenantCreate, TenantUpdate, TenantResponse, TenantPaginatedResponse
|
|
from app.middleware.auth_middleware import get_current_user, require_access, User
|
|
from app.middleware.tenant_middleware import get_tenant_id_from_user
|
|
from app.services.system.audit_log_service import AuditLogService
|
|
from app.services.auth.event_service import EventService
|
|
from app.helper.helpers import get_client_ip
|
|
|
|
router = APIRouter()
|
|
|
|
@router.post("/create", response_model=TenantResponse, status_code=status.HTTP_201_CREATED)
|
|
def create_tenant(
|
|
request: Request,
|
|
tenant_data: TenantCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
_ = Depends(require_access("superadmin.tenant.create"))
|
|
):
|
|
try:
|
|
# 1. Create tenant/owner/modules/events using flush
|
|
result = TenantController.create_tenant(db, tenant_data, commit=False)
|
|
|
|
# 2. Sanitize payload for audit log (never store raw passwords or hashes)
|
|
audit_values = tenant_data.model_dump(mode='json')
|
|
if "owner" in audit_values and isinstance(audit_values["owner"], dict):
|
|
audit_values["owner"]["password"] = "[PROTECTED]"
|
|
|
|
# 3. Create sanitized audit record using flush (strict=True)
|
|
AuditLogService.log(
|
|
db=db,
|
|
module_name="Tenants",
|
|
action_type="CREATE",
|
|
entity_id=str(result.id),
|
|
entity_name=result.tenant_name,
|
|
description=f"Tenant '{result.tenant_name}' onboarded",
|
|
performed_by_id=str(current_user.id),
|
|
performed_by_email=current_user.email,
|
|
ip_address=get_client_ip(request),
|
|
new_values=audit_values,
|
|
commit=False,
|
|
strict=True,
|
|
)
|
|
|
|
# 4. Perform single final commit
|
|
db.commit()
|
|
db.refresh(result)
|
|
|
|
# 5. Best-effort post-commit Redis wake-up notification (never breaks response)
|
|
pending_ids = getattr(result, "pending_event_ids", [])
|
|
if pending_ids:
|
|
EventService.enqueue_wakeups(pending_ids)
|
|
|
|
return result
|
|
except Exception:
|
|
db.rollback()
|
|
raise
|
|
|
|
@router.get("/get", response_model=List[TenantResponse])
|
|
def get_all_tenants(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
_ = Depends(require_access("superadmin.tenant.read"))
|
|
):
|
|
# READ actions are typically not logged to avoid DB bloat
|
|
return TenantController.get_all_tenants(db)
|
|
|
|
@router.get("/me", response_model=TenantResponse)
|
|
def get_my_tenant(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = get_tenant_id_from_user(current_user)
|
|
if not tenant_id:
|
|
raise HTTPException(status_code=404, detail="Tenant not found")
|
|
return TenantController.get_tenant_by_id(db, tenant_id)
|
|
|
|
@router.put("/update/{tenant_id}", response_model=TenantResponse)
|
|
def update_tenant(
|
|
request: Request,
|
|
tenant_id: uuid.UUID,
|
|
tenant_data: TenantUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
_ = Depends(require_access("superadmin.tenant.update"))
|
|
):
|
|
# 1. Get snapshot BEFORE update for Audit Log
|
|
existing = TenantController.get_tenant_by_id(db, tenant_id)
|
|
old_snapshot = {
|
|
"tenant_name": existing.tenant_name,
|
|
"tenant_domain": existing.tenant_domain,
|
|
"tenant_logo_url": existing.tenant_logo_url,
|
|
"is_active": existing.is_active,
|
|
"plan_id": str(existing.plan_id) if existing.plan_id else None,
|
|
"start_date": existing.start_date.isoformat() if existing.start_date else None,
|
|
"end_date": existing.end_date.isoformat() if existing.end_date else None,
|
|
"status": existing.status,
|
|
}
|
|
|
|
# 2. Perform update
|
|
result = TenantController.update_tenant(db, tenant_id, tenant_data)
|
|
|
|
# 3. Prepare new values and indentify deltas
|
|
new_snapshot = tenant_data.model_dump(mode='json', exclude_unset=True)
|
|
|
|
old_values = {k: old_snapshot[k] for k in new_snapshot if k in old_snapshot and old_snapshot[k] != new_snapshot[k]}
|
|
new_values = {k: new_snapshot[k] for k in old_values}
|
|
|
|
# 4. Log the change
|
|
AuditLogService.log(
|
|
db=db,
|
|
module_name="Tenants",
|
|
action_type="UPDATE",
|
|
entity_id=str(tenant_id),
|
|
entity_name=result.tenant_name,
|
|
description=f"Tenant '{result.tenant_name}' updated",
|
|
performed_by_id=str(current_user.id),
|
|
performed_by_email=current_user.email,
|
|
ip_address=get_client_ip(request),
|
|
old_values=old_values,
|
|
new_values=new_values
|
|
)
|
|
return result
|
|
|
|
@router.delete("/delete/{tenant_id}")
|
|
def delete_tenant(
|
|
request: Request,
|
|
tenant_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
_ = Depends(require_access("superadmin.tenant.delete"))
|
|
):
|
|
# 1. Get snapshot before deletion
|
|
existing = TenantController.get_tenant_by_id(db, tenant_id)
|
|
|
|
# 2. Perform deletion
|
|
result = TenantController.delete_tenant(db, tenant_id)
|
|
|
|
# 3. Log the deletion
|
|
AuditLogService.log(
|
|
db=db,
|
|
module_name="Tenants",
|
|
action_type="DELETE",
|
|
entity_id=str(tenant_id),
|
|
entity_name=existing.tenant_name,
|
|
description=f"Tenant '{existing.tenant_name}' removed",
|
|
performed_by_id=str(current_user.id),
|
|
performed_by_email=current_user.email,
|
|
ip_address=get_client_ip(request),
|
|
old_values={"tenant_name": existing.tenant_name, "domain": existing.tenant_domain}
|
|
)
|
|
return result
|
|
|
|
@router.get("/list", response_model=TenantPaginatedResponse)
|
|
def list_tenants(
|
|
page: int = Query(1, ge=1, description="Page number"),
|
|
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
|
search: Optional[str] = Query(None, description="Search in name, domain, or ID"),
|
|
is_active: Optional[bool] = Query(None, description="Filter by active status"),
|
|
filter_tenant_names: Optional[List[str]] = Query(None, description="Filter by tenant names"),
|
|
filter_tenant_domains: Optional[List[str]] = Query(None, description="Filter by tenant domains"),
|
|
filter_plan_ids: Optional[List[uuid.UUID]] = Query(None, description="Filter by plan IDs"),
|
|
statuses: Optional[List[bool]] = Query(None, description="Filter by tenant statuses"),
|
|
sort_by: Optional[str] = Query(None, description="Sort by name, domain, status, or plan"),
|
|
sort_order: Optional[str] = Query(None, description="Sort order: asc or desc"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
_ = Depends(require_access("superadmin.tenant.read")),
|
|
):
|
|
return TenantController.get_tenants_paginated(
|
|
db=db,
|
|
page=page,
|
|
page_size=page_size,
|
|
search=search,
|
|
is_active=is_active,
|
|
filter_tenant_names=filter_tenant_names,
|
|
filter_tenant_domains=filter_tenant_domains,
|
|
filter_plan_ids=filter_plan_ids,
|
|
statuses=statuses,
|
|
sort_by=sort_by,
|
|
sort_order=sort_order,
|
|
) |