"""HTTP surface for policy notices (制度中心 / 制度公告). Two router prefixes mounted from a single module so the auth gates are explicit at the route layer: * ``/api/v1/policy-notices`` — read-only, ANY authenticated user. ``GET ""`` backs the header banner (pinned subset); ``GET "/library"`` backs the employee 制度中心 searchable library. * ``/api/v1/admin/policy-notices`` — full CRUD + reorder, restricted to ``global_admin``. The management role (``management``) is READ-ONLY platform-wide and deliberately excluded — the brief is "leadership view, no operational changes". """ from __future__ import annotations import uuid from fastapi import APIRouter, Depends, Query, Request, status from sqlalchemy.ext.asyncio import AsyncSession from app.auth.models import AuthUser from app.core.database import get_db from app.core.dependencies import get_current_user, require_admin, require_config_read from app.core.response import success_response from app.domains.policy_notices.schemas import ( PolicyNoticeAdmin, PolicyNoticeBanner, PolicyNoticeCreate, PolicyNoticeLibrary, PolicyNoticeReorder, PolicyNoticeUpdate, ) from app.domains.policy_notices.service import PolicyNoticesService # ── Public read ───────────────────────────────────────────────────── public_router = APIRouter( prefix="/api/v1/policy-notices", tags=["policy-notices"], ) @public_router.get("") async def list_banner( _: AuthUser = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Return the pinned banner subset, ordered. Available to any signed-in user — the header ribbon renders for everyone, including auditors / hr / finance / management.""" svc = PolicyNoticesService(db) rows = await svc.list_banner() return success_response( data=[PolicyNoticeBanner.model_validate(r).model_dump(mode="json") for r in rows], ) @public_router.get("/library") async def list_library( q: str | None = Query(default=None, max_length=200), _: AuthUser = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Return the published policy library (制度中心), optionally filtered by a free-text query matched against title / summary / keywords in either language. Available to any signed-in user — read-only.""" svc = PolicyNoticesService(db) rows = await svc.list_library(q=q) return success_response( data=[PolicyNoticeLibrary.model_validate(r).model_dump(mode="json") for r in rows], ) # ── Admin CRUD ────────────────────────────────────────────────────── admin_router = APIRouter( prefix="/api/v1/admin/policy-notices", tags=["policy-notices-admin"], ) @admin_router.get("") async def list_admin( _: AuthUser = Depends(require_config_read()), db: AsyncSession = Depends(get_db), ): svc = PolicyNoticesService(db) rows = await svc.list_all() return success_response( data=[PolicyNoticeAdmin.model_validate(r).model_dump(mode="json") for r in rows], ) @admin_router.post("", status_code=status.HTTP_201_CREATED) async def create_admin( body: PolicyNoticeCreate, request: Request, user: AuthUser = Depends(require_admin()), db: AsyncSession = Depends(get_db), ): svc = PolicyNoticesService(db) row = await svc.create( body, actor_id=user.id, trace_id=getattr(request.state, "trace_id", ""), ) return success_response( data=PolicyNoticeAdmin.model_validate(row).model_dump(mode="json"), ) @admin_router.put("/{notice_id}") async def update_admin( notice_id: uuid.UUID, body: PolicyNoticeUpdate, request: Request, user: AuthUser = Depends(require_admin()), db: AsyncSession = Depends(get_db), ): svc = PolicyNoticesService(db) row = await svc.update( notice_id, body, actor_id=user.id, trace_id=getattr(request.state, "trace_id", ""), ) return success_response( data=PolicyNoticeAdmin.model_validate(row).model_dump(mode="json"), ) @admin_router.delete("/{notice_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_admin( notice_id: uuid.UUID, request: Request, user: AuthUser = Depends(require_admin()), db: AsyncSession = Depends(get_db), ): svc = PolicyNoticesService(db) await svc.delete( notice_id, actor_id=user.id, trace_id=getattr(request.state, "trace_id", ""), ) return None @admin_router.post("/reorder") async def reorder_admin( body: PolicyNoticeReorder, request: Request, user: AuthUser = Depends(require_admin()), db: AsyncSession = Depends(get_db), ): svc = PolicyNoticesService(db) rows = await svc.reorder( body.ordered_ids, actor_id=user.id, trace_id=getattr(request.state, "trace_id", ""), ) return success_response( data=[PolicyNoticeAdmin.model_validate(r).model_dump(mode="json") for r in rows], )