feat: add multi-tenant system with properties, organizations, and public booking

Implement complete multi-property architecture:
- Properties (groups of spaces) with public/private visibility
- Property managers (many-to-many) with role-based permissions
- Organizations with member management
- Anonymous/guest booking support via public API (/api/public/*)
- Property-scoped spaces, bookings, and settings
- Frontend: property selector, organization management, public booking views
- Migration script and updated seed data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-15 00:17:21 +00:00
parent d637513d92
commit e21cf03a16
51 changed files with 6324 additions and 273 deletions

View File

@@ -5,7 +5,8 @@ from typing import Annotated, Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session, joinedload
from app.core.deps import get_current_admin, get_db
from app.core.deps import get_current_manager_or_superadmin, get_db
from app.core.permissions import get_manager_property_ids
from app.models.audit_log import AuditLog
from app.models.user import User
from app.schemas.audit_log import AuditLogRead
@@ -21,15 +22,22 @@ def get_audit_logs(
page: Annotated[int, Query(ge=1)] = 1,
limit: Annotated[int, Query(ge=1, le=100)] = 50,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin),
current_admin: User = Depends(get_current_manager_or_superadmin),
) -> list[AuditLogRead]:
"""
Get audit logs with filtering and pagination.
Admin only endpoint to view audit trail of administrative actions.
Managers see only logs related to their managed properties (booking/space actions).
"""
query = db.query(AuditLog).options(joinedload(AuditLog.user))
# Property scoping for managers - only show relevant actions
if current_admin.role == "manager":
managed_ids = get_manager_property_ids(db, current_admin.id)
# Managers see: their own actions + actions on bookings/spaces in their properties
query = query.filter(AuditLog.user_id == current_admin.id)
# Apply filters
if action:
query = query.filter(AuditLog.action == action)