Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
217 lines
7.0 KiB
Python
217 lines
7.0 KiB
Python
"""Member APIs — Phase 7.0 foundation + Phase 7.1 membership lifecycle."""
|
|
from __future__ import annotations
|
|
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, Query, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.api.deps import get_db, get_pagination, require_tenant
|
|
from app.api.permissions import require_permissions
|
|
from app.permissions.definitions import (
|
|
LOYALTY_MEMBERS_ACTIVATE,
|
|
LOYALTY_MEMBERS_CANCEL,
|
|
LOYALTY_MEMBERS_CREATE,
|
|
LOYALTY_MEMBERS_DELETE,
|
|
LOYALTY_MEMBERS_ENROLL,
|
|
LOYALTY_MEMBERS_EXPIRE,
|
|
LOYALTY_MEMBERS_FREEZE,
|
|
LOYALTY_MEMBERS_LIFECYCLE_VIEW,
|
|
LOYALTY_MEMBERS_RENEW,
|
|
LOYALTY_MEMBERS_RESUME,
|
|
LOYALTY_MEMBERS_TRANSFER,
|
|
LOYALTY_MEMBERS_UPDATE,
|
|
LOYALTY_MEMBERS_VIEW,
|
|
)
|
|
from app.schemas.foundation import (
|
|
MemberActivateRequest,
|
|
MemberCancelRequest,
|
|
MemberCreate,
|
|
MemberEnrollRequest,
|
|
MemberExpireRequest,
|
|
MemberFreezeRequest,
|
|
MemberRead,
|
|
MemberRenewRequest,
|
|
MemberResumeRequest,
|
|
MembershipLifecycleEventRead,
|
|
MemberTransferRequest,
|
|
MemberUpdate,
|
|
)
|
|
from app.services.foundation import MemberService
|
|
from app.services.membership_engine import MembershipEngineService
|
|
from shared.pagination import PaginationParams
|
|
from shared.security import CurrentUser
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("", response_model=MemberRead, status_code=status.HTTP_201_CREATED)
|
|
async def create_member(
|
|
body: MemberCreate,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_CREATE)),
|
|
):
|
|
return await MemberService(db).create(tenant_id, body, actor=user)
|
|
|
|
|
|
@router.get("", response_model=list[MemberRead])
|
|
async def list_members(
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
pagination: PaginationParams = Depends(get_pagination),
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_VIEW)),
|
|
):
|
|
return await MemberService(db).list(
|
|
tenant_id, offset=pagination.offset, limit=pagination.page_size
|
|
)
|
|
|
|
|
|
@router.get("/{member_id}", response_model=MemberRead)
|
|
async def get_member(
|
|
member_id: UUID,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_VIEW)),
|
|
):
|
|
return await MemberService(db).get(tenant_id, member_id)
|
|
|
|
|
|
@router.patch("/{member_id}", response_model=MemberRead)
|
|
async def update_member(
|
|
member_id: UUID,
|
|
body: MemberUpdate,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_UPDATE)),
|
|
):
|
|
return await MemberService(db).update(tenant_id, member_id, body, actor=user)
|
|
|
|
|
|
@router.post("/{member_id}/enroll", response_model=MemberRead)
|
|
async def enroll_member(
|
|
member_id: UUID,
|
|
body: MemberEnrollRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_ENROLL)),
|
|
):
|
|
return await MemberService(db).enroll(tenant_id, member_id, body, actor=user)
|
|
|
|
|
|
@router.post("/{member_id}/activate", response_model=MemberRead)
|
|
async def activate_member(
|
|
member_id: UUID,
|
|
body: MemberActivateRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_ACTIVATE)),
|
|
):
|
|
return await MembershipEngineService(db).activate(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/renew", response_model=MemberRead)
|
|
async def renew_member(
|
|
member_id: UUID,
|
|
body: MemberRenewRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_RENEW)),
|
|
):
|
|
return await MembershipEngineService(db).renew(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/freeze", response_model=MemberRead)
|
|
async def freeze_member(
|
|
member_id: UUID,
|
|
body: MemberFreezeRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_FREEZE)),
|
|
):
|
|
return await MembershipEngineService(db).freeze(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/resume", response_model=MemberRead)
|
|
async def resume_member(
|
|
member_id: UUID,
|
|
body: MemberResumeRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_RESUME)),
|
|
):
|
|
return await MembershipEngineService(db).resume(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/cancel", response_model=MemberRead)
|
|
async def cancel_member(
|
|
member_id: UUID,
|
|
body: MemberCancelRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_CANCEL)),
|
|
):
|
|
return await MembershipEngineService(db).cancel(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/expire", response_model=MemberRead)
|
|
async def expire_member(
|
|
member_id: UUID,
|
|
body: MemberExpireRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_EXPIRE)),
|
|
):
|
|
return await MembershipEngineService(db).expire(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/transfer", response_model=MemberRead)
|
|
async def transfer_member(
|
|
member_id: UUID,
|
|
body: MemberTransferRequest,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_TRANSFER)),
|
|
):
|
|
return await MembershipEngineService(db).transfer(
|
|
tenant_id, member_id, body, actor=user
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/{member_id}/lifecycle",
|
|
response_model=list[MembershipLifecycleEventRead],
|
|
)
|
|
async def list_member_lifecycle(
|
|
member_id: UUID,
|
|
limit: int = Query(default=100, ge=1, le=500),
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_LIFECYCLE_VIEW)),
|
|
):
|
|
return await MembershipEngineService(db).list_lifecycle(
|
|
tenant_id, member_id, limit=limit
|
|
)
|
|
|
|
|
|
@router.post("/{member_id}/delete", response_model=MemberRead)
|
|
async def soft_delete_member(
|
|
member_id: UUID,
|
|
tenant_id: UUID = Depends(require_tenant),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: CurrentUser = Depends(require_permissions(LOYALTY_MEMBERS_DELETE)),
|
|
):
|
|
return await MemberService(db).soft_delete(tenant_id, member_id, actor=user)
|