微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
"""
管理端审核路由 —— 申请列表、详情、批准、拒绝。
端点清单:
- GET /api/admin/applications — 查询申请列表(可按 status 过滤)
- GET /api/admin/applications/{id} — 查询申请详情 + 候选匹配
- POST /api/admin/applications/{id}/approve — 批准申请
- POST /api/admin/applications/{id}/reject — 拒绝申请
所有端点要求 JWT + site_admin 或 tenant_admin 角色。
"""
from __future__ import annotations
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from app.auth.dependencies import CurrentUser
from app.database import get_connection
from app.middleware.permission import require_permission
from app.schemas.xcx_auth import (
ApplicationResponse,
ApproveRequest,
MatchCandidate,
RejectRequest,
)
from app.services.application import approve_application, reject_application
from app.services.matching import find_candidates
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/admin", tags=["管理端审核"])
# ── 管理端需要 site_admin 或 tenant_admin 权限 ─────────────
# require_permission() 不检查具体 permission code
# 但会验证 status=approved管理端路由额外在依赖中检查角色。
# 设计文档要求 site_admin / tenant_admin 角色,
# 这里通过检查 CurrentUser.roles 实现。
def _require_admin():
"""
管理端依赖:要求用户 status=approved 且角色包含 site_admin 或 tenant_admin。
复用 require_permission()(无具体权限码 → 仅检查 approved
再额外校验角色列表。
"""
async def _dependency(
user: CurrentUser = Depends(require_permission()),
) -> CurrentUser:
admin_roles = {"site_admin", "tenant_admin"}
if not admin_roles.intersection(user.roles):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="需要管理员权限site_admin 或 tenant_admin",
)
return user
return _dependency
# ── GET /api/admin/applications ───────────────────────────
@router.get("/applications", response_model=list[ApplicationResponse])
async def list_applications(
status_filter: Optional[str] = Query(
None, alias="status", description="按状态过滤pending / approved / rejected"
),
user: CurrentUser = Depends(_require_admin()),
):
"""查询申请列表,可按 status 过滤。"""
conn = get_connection()
try:
with conn.cursor() as cur:
if status_filter:
cur.execute(
"""
SELECT id, site_code, applied_role_text, status,
review_note, created_at::text, reviewed_at::text
FROM auth.user_applications
WHERE status = %s
ORDER BY created_at DESC
""",
(status_filter,),
)
else:
cur.execute(
"""
SELECT id, site_code, applied_role_text, status,
review_note, created_at::text, reviewed_at::text
FROM auth.user_applications
ORDER BY created_at DESC
"""
)
rows = cur.fetchall()
finally:
conn.close()
return [
ApplicationResponse(
id=r[0],
site_code=r[1],
applied_role_text=r[2],
status=r[3],
review_note=r[4],
created_at=r[5],
reviewed_at=r[6],
)
for r in rows
]
# ── GET /api/admin/applications/{id} ─────────────────────
@router.get("/applications/{application_id}")
async def get_application_detail(
application_id: int,
user: CurrentUser = Depends(_require_admin()),
):
"""查询申请详情 + 候选匹配。"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT id, user_id, site_code, site_id, applied_role_text,
phone, employee_number, status,
review_note, created_at::text, reviewed_at::text
FROM auth.user_applications
WHERE id = %s
""",
(application_id,),
)
row = cur.fetchone()
finally:
conn.close()
if row is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="申请不存在",
)
app_data = {
"id": row[0],
"user_id": row[1],
"site_code": row[2],
"site_id": row[3],
"applied_role_text": row[4],
"phone": row[5],
"employee_number": row[6],
"status": row[7],
"review_note": row[8],
"created_at": row[9],
"reviewed_at": row[10],
}
# 查找候选匹配
candidates_raw = await find_candidates(
site_id=app_data["site_id"],
phone=app_data["phone"],
employee_number=app_data["employee_number"],
)
candidates = [MatchCandidate(**c) for c in candidates_raw]
return {
"application": app_data,
"candidates": [c.model_dump() for c in candidates],
}
# ── POST /api/admin/applications/{id}/approve ────────────
@router.post("/applications/{application_id}/approve", response_model=ApplicationResponse)
async def approve(
application_id: int,
body: ApproveRequest,
user: CurrentUser = Depends(_require_admin()),
):
"""批准申请:分配角色 + 可选绑定。"""
result = await approve_application(
application_id=application_id,
reviewer_id=user.user_id,
role_id=body.role_id,
binding=body.binding,
review_note=body.review_note,
)
return ApplicationResponse(**result)
# ── POST /api/admin/applications/{id}/reject ─────────────
@router.post("/applications/{application_id}/reject", response_model=ApplicationResponse)
async def reject(
application_id: int,
body: RejectRequest,
user: CurrentUser = Depends(_require_admin()),
):
"""拒绝申请:记录拒绝原因。"""
result = await reject_application(
application_id=application_id,
reviewer_id=user.user_id,
review_note=body.review_note,
)
return ApplicationResponse(**result)