video-compliance-ai/backend/app/api/organizations.py
Your Name a32102f583 feat: 补全后端 API 并对齐前后端类型
- 后端新增: Project CRUD / Brief CRUD / 组织关系管理 / 工作台统计 / SSE 推送 / 认证依赖注入
- 后端完善: 任务 API 全流程(创建/审核/申诉) + Task Service + Task Schema
- 前端修复: login 页面 localStorage key 错误 (miaosi_auth -> miaosi_user)
- 前端对齐: types/task.ts 与后端 TaskStage/TaskResponse 完全对齐
- 前端新增: project/brief/organization/dashboard 类型定义
- 前端补全: api.ts 新增 30+ API 方法覆盖所有后端接口

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 14:13:08 +08:00

323 lines
9.1 KiB
Python

"""
组织关系 API
品牌方管理代理商,代理商管理达人
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload
from app.database import get_db
from app.models.user import User, UserRole
from app.models.organization import (
Brand, Agency, Creator,
brand_agency_association, agency_creator_association,
)
from app.api.deps import get_current_user, get_current_brand, get_current_agency
from app.schemas.organization import (
BrandSummary,
AgencySummary,
CreatorSummary,
InviteAgencyRequest,
InviteCreatorRequest,
UpdateAgencyPermissionRequest,
AgencyListResponse,
CreatorListResponse,
BrandListResponse,
)
router = APIRouter(prefix="/organizations", tags=["组织关系"])
# ===== 品牌方管理代理商 =====
@router.get("/brand/agencies", response_model=AgencyListResponse)
async def list_brand_agencies(
brand: Brand = Depends(get_current_brand),
db: AsyncSession = Depends(get_db),
):
"""查询品牌方的代理商列表"""
result = await db.execute(
select(Brand)
.options(selectinload(Brand.agencies))
.where(Brand.id == brand.id)
)
brand_with_agencies = result.scalar_one()
items = [
AgencySummary(
id=a.id,
name=a.name,
logo=a.logo,
contact_name=a.contact_name,
force_pass_enabled=a.force_pass_enabled,
)
for a in brand_with_agencies.agencies
]
return AgencyListResponse(items=items, total=len(items))
@router.post("/brand/agencies", status_code=status.HTTP_201_CREATED)
async def invite_agency(
request: InviteAgencyRequest,
brand: Brand = Depends(get_current_brand),
db: AsyncSession = Depends(get_db),
):
"""邀请代理商加入品牌方"""
# 查找代理商
result = await db.execute(
select(Agency).where(Agency.id == request.agency_id)
)
agency = result.scalar_one_or_none()
if not agency:
raise HTTPException(status_code=404, detail="代理商不存在")
# 检查是否已关联
brand_result = await db.execute(
select(Brand)
.options(selectinload(Brand.agencies))
.where(Brand.id == brand.id)
)
brand_with_agencies = brand_result.scalar_one()
if agency in brand_with_agencies.agencies:
raise HTTPException(status_code=400, detail="该代理商已加入")
brand_with_agencies.agencies.append(agency)
await db.flush()
return {"message": "邀请成功", "agency_id": agency.id}
@router.delete("/brand/agencies/{agency_id}")
async def remove_agency(
agency_id: str,
brand: Brand = Depends(get_current_brand),
db: AsyncSession = Depends(get_db),
):
"""移除代理商"""
brand_result = await db.execute(
select(Brand)
.options(selectinload(Brand.agencies))
.where(Brand.id == brand.id)
)
brand_with_agencies = brand_result.scalar_one()
agency_result = await db.execute(
select(Agency).where(Agency.id == agency_id)
)
agency = agency_result.scalar_one_or_none()
if agency and agency in brand_with_agencies.agencies:
brand_with_agencies.agencies.remove(agency)
await db.flush()
return {"message": "已移除"}
@router.put("/brand/agencies/{agency_id}/permission")
async def update_agency_permission(
agency_id: str,
request: UpdateAgencyPermissionRequest,
brand: Brand = Depends(get_current_brand),
db: AsyncSession = Depends(get_db),
):
"""更新代理商权限(如强制通过权)"""
# 验证代理商是否属于该品牌
brand_result = await db.execute(
select(Brand)
.options(selectinload(Brand.agencies))
.where(Brand.id == brand.id)
)
brand_with_agencies = brand_result.scalar_one()
agency_result = await db.execute(
select(Agency).where(Agency.id == agency_id)
)
agency = agency_result.scalar_one_or_none()
if not agency or agency not in brand_with_agencies.agencies:
raise HTTPException(status_code=404, detail="代理商不存在或未加入")
agency.force_pass_enabled = request.force_pass_enabled
await db.flush()
return {"message": "权限已更新"}
# ===== 代理商管理达人 =====
@router.get("/agency/creators", response_model=CreatorListResponse)
async def list_agency_creators(
agency: Agency = Depends(get_current_agency),
db: AsyncSession = Depends(get_db),
):
"""查询代理商的达人列表"""
result = await db.execute(
select(Agency)
.options(selectinload(Agency.creators))
.where(Agency.id == agency.id)
)
agency_with_creators = result.scalar_one()
items = [
CreatorSummary(
id=c.id,
name=c.name,
avatar=c.avatar,
douyin_account=c.douyin_account,
xiaohongshu_account=c.xiaohongshu_account,
bilibili_account=c.bilibili_account,
)
for c in agency_with_creators.creators
]
return CreatorListResponse(items=items, total=len(items))
@router.post("/agency/creators", status_code=status.HTTP_201_CREATED)
async def invite_creator(
request: InviteCreatorRequest,
agency: Agency = Depends(get_current_agency),
db: AsyncSession = Depends(get_db),
):
"""邀请达人加入代理商"""
result = await db.execute(
select(Creator).where(Creator.id == request.creator_id)
)
creator = result.scalar_one_or_none()
if not creator:
raise HTTPException(status_code=404, detail="达人不存在")
agency_result = await db.execute(
select(Agency)
.options(selectinload(Agency.creators))
.where(Agency.id == agency.id)
)
agency_with_creators = agency_result.scalar_one()
if creator in agency_with_creators.creators:
raise HTTPException(status_code=400, detail="该达人已加入")
agency_with_creators.creators.append(creator)
await db.flush()
return {"message": "邀请成功", "creator_id": creator.id}
@router.delete("/agency/creators/{creator_id}")
async def remove_creator(
creator_id: str,
agency: Agency = Depends(get_current_agency),
db: AsyncSession = Depends(get_db),
):
"""移除达人"""
agency_result = await db.execute(
select(Agency)
.options(selectinload(Agency.creators))
.where(Agency.id == agency.id)
)
agency_with_creators = agency_result.scalar_one()
creator_result = await db.execute(
select(Creator).where(Creator.id == creator_id)
)
creator = creator_result.scalar_one_or_none()
if creator and creator in agency_with_creators.creators:
agency_with_creators.creators.remove(creator)
await db.flush()
return {"message": "已移除"}
# ===== 代理商查看关联品牌方 =====
@router.get("/agency/brands", response_model=BrandListResponse)
async def list_agency_brands(
agency: Agency = Depends(get_current_agency),
db: AsyncSession = Depends(get_db),
):
"""查询代理商关联的品牌方列表"""
result = await db.execute(
select(Agency)
.options(selectinload(Agency.brands))
.where(Agency.id == agency.id)
)
agency_with_brands = result.scalar_one()
items = [
BrandSummary(
id=b.id,
name=b.name,
logo=b.logo,
contact_name=b.contact_name,
)
for b in agency_with_brands.brands
]
return BrandListResponse(items=items, total=len(items))
# ===== 搜索(用于邀请时查找) =====
@router.get("/search/agencies")
async def search_agencies(
keyword: str = Query(..., min_length=1),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""搜索代理商(用于邀请)"""
result = await db.execute(
select(Agency)
.where(Agency.name.ilike(f"%{keyword}%"))
.limit(20)
)
agencies = list(result.scalars().all())
return {
"items": [
AgencySummary(
id=a.id,
name=a.name,
logo=a.logo,
contact_name=a.contact_name,
force_pass_enabled=a.force_pass_enabled,
).model_dump()
for a in agencies
]
}
@router.get("/search/creators")
async def search_creators(
keyword: str = Query(..., min_length=1),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""搜索达人(用于邀请)"""
result = await db.execute(
select(Creator)
.where(Creator.name.ilike(f"%{keyword}%"))
.limit(20)
)
creators = list(result.scalars().all())
return {
"items": [
CreatorSummary(
id=c.id,
name=c.name,
avatar=c.avatar,
douyin_account=c.douyin_account,
xiaohongshu_account=c.xiaohongshu_account,
bilibili_account=c.bilibili_account,
).model_dump()
for c in creators
]
}