- 后端新增: 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>
323 lines
9.1 KiB
Python
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
|
|
]
|
|
}
|