爆款开头API开发完成
This commit is contained in:
@@ -0,0 +1,527 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_current_user, get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.hot_opening_replicate import (
|
||||
HotOpeningActionOut,
|
||||
HotOpeningDeleteOut,
|
||||
HotOpeningGenerateImagePromptRequest,
|
||||
HotOpeningGenerateImageRequest,
|
||||
HotOpeningGenerateVideoPromptRequest,
|
||||
HotOpeningGenerateVideoRequest,
|
||||
HotOpeningImagePromptUpdateRequest,
|
||||
HotOpeningMaterialUpdateRequest,
|
||||
HotOpeningSpecOut,
|
||||
HotOpeningTaskCreate,
|
||||
HotOpeningTaskDetailOut,
|
||||
HotOpeningTaskListOut,
|
||||
HotOpeningVideoPromptSchemaUpdateRequest,
|
||||
)
|
||||
from app.services.hot_opening_replicate_service import (
|
||||
_get_project_for_user,
|
||||
create_hot_opening_project,
|
||||
delete_hot_opening_project,
|
||||
generate_image_from_prompt,
|
||||
generate_video_from_prompt,
|
||||
list_hot_opening_projects,
|
||||
mark_hot_opening_step_dispatch_failed,
|
||||
project_to_detail_out,
|
||||
submit_image_prompt_optimize,
|
||||
submit_video_prompt_optimize,
|
||||
update_hot_opening_image_prompt,
|
||||
update_hot_opening_material_input,
|
||||
update_hot_opening_video_prompt_schema,
|
||||
)
|
||||
from app.tasks.celery_app import celery_app
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/hot-opening-replications",
|
||||
tags=["hot-opening-replications"],
|
||||
)
|
||||
|
||||
|
||||
async def _reload_project_detail(
|
||||
db: AsyncSession,
|
||||
current_user: User,
|
||||
project_id: str,
|
||||
) -> HotOpeningTaskDetailOut:
|
||||
"""提交事务后统一重新查询详情,避免继续访问 commit 前 ORM 对象。"""
|
||||
project = await _get_project_for_user(
|
||||
db,
|
||||
project_id=project_id,
|
||||
user=current_user,
|
||||
for_update=False,
|
||||
populate_existing=True,
|
||||
)
|
||||
return await project_to_detail_out(db, project)
|
||||
|
||||
|
||||
async def _mark_dispatch_failed_and_raise(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
current_user: User,
|
||||
project_id: str,
|
||||
step_id: str | None,
|
||||
message: str,
|
||||
) -> None:
|
||||
"""Celery 投递失败后,数据库事务已提交,单独标记步骤失败,避免一直 processing。"""
|
||||
if step_id:
|
||||
try:
|
||||
await mark_hot_opening_step_dispatch_failed(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
error_message=message,
|
||||
)
|
||||
await db.commit()
|
||||
except Exception:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=503, detail=message)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/spec",
|
||||
response_model=HotOpeningSpecOut,
|
||||
summary="查询爆款开头复刻模块状态枚举和步骤 JSON 结构说明",
|
||||
description="返回总任务状态、子任务状态、5个固定步骤编码以及每个步骤 input_json/output_json 的统一结构示例,方便前端和排查人员对照。",
|
||||
)
|
||||
async def get_spec():
|
||||
return HotOpeningSpecOut()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tasks",
|
||||
response_model=HotOpeningTaskDetailOut,
|
||||
summary="创建爆款开头复刻总任务项目",
|
||||
description=(
|
||||
"创建爆款开头复刻总任务项目。总任务表 id 就是项目ID,不再传 project_id。"
|
||||
"接口只同步创建第1个素材输入子任务,保存素材视频链接、素材图片链接、视频素材内容项目名称、生成项目名称和50字核心内容点。"
|
||||
"后端不开发上传接口,也不校验素材文件时长、大小、格式,直接使用前端已有上传接口返回的链接。"
|
||||
"创建后不会自动生成第2步图片 AI 提词,需要前端手动调用 generate-image-prompt。"
|
||||
),
|
||||
)
|
||||
async def create_task(
|
||||
req: HotOpeningTaskCreate = Body(..., description="爆款开头复刻创建参数,只包含素材链接和项目描述,不包含图片/视频引擎参数"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
project = await create_hot_opening_project(db, current_user, req)
|
||||
project_id_value = str(project.id)
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"创建爆款开头复刻项目失败: {exc}")
|
||||
|
||||
return await _reload_project_detail(db, current_user, project_id_value)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/tasks",
|
||||
response_model=HotOpeningTaskListOut,
|
||||
summary="查询爆款开头复刻总任务项目列表",
|
||||
description="分页查询爆款开头复刻总任务项目列表。普通用户只能查看自己的项目,管理员可查看全部。",
|
||||
)
|
||||
async def list_tasks(
|
||||
status: str | None = Query(None, description="总任务状态筛选,例如 waiting_user、processing、completed、failed;为空不过滤"),
|
||||
page: int = Query(1, ge=1, description="分页页码,从1开始"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量,范围1-100"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
return await list_hot_opening_projects(db, current_user=current_user, status=status, page=page, page_size=page_size)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/tasks/{project_id}",
|
||||
response_model=HotOpeningTaskDetailOut,
|
||||
summary="获取爆款开头复刻总任务项目详情",
|
||||
description=(
|
||||
"获取爆款开头复刻总任务详情。详情会聚合返回第1步素材信息、第2步图片提词、第3步图片引擎和参数、"
|
||||
"第4步视频提词 JSON schema、第5步视频引擎和参数、最终图片、最终视频和完整子任务列表。"
|
||||
),
|
||||
)
|
||||
async def get_task(
|
||||
project_id: str = Path(..., description="总任务项目ID,即 module_generation_projects.id"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
return await _reload_project_detail(db, current_user, project_id)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tasks/{project_id}/material",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="修改第1步素材输入并重建第1步新版本",
|
||||
description=(
|
||||
"反复修改爆款开头复刻第1步素材输入。"
|
||||
"接口会软删除旧第1步以及第2、3、4、5步当前有效子任务,联动软删除关联 ChatGenerationTask,"
|
||||
"然后新建第1步 material_input 的 version+1,项目回到 waiting_user 状态。"
|
||||
),
|
||||
)
|
||||
async def update_material(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
req: HotOpeningMaterialUpdateRequest = Body(..., description="第1步素材输入修改参数,至少传一个字段"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
project_id_value, step_id_value = await update_hot_opening_material_input(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
req=req,
|
||||
)
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"修改素材输入失败: {exc}")
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="素材输入已修改,旧步骤已软删除,请重新生成图片 AI 提词",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tasks/{project_id}/steps/{step_id}/image-prompt",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="直接修改第2步图片 AI 优化提词",
|
||||
description=(
|
||||
"直接修改第2步图片 AI 优化提词,不调用 AI、不扣积分。"
|
||||
"保存后会软删除第3、4、5步当前有效任务和关联 ChatGenerationTask,"
|
||||
"清空旧图片/视频结果,让用户从图片生成开始重新执行。"
|
||||
),
|
||||
)
|
||||
async def update_image_prompt(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
step_id: str = Path(..., description="第2步图片 AI 提词子任务ID"),
|
||||
req: HotOpeningImagePromptUpdateRequest = Body(..., description="图片 AI 优化提词修改参数"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
project, step = await update_hot_opening_image_prompt(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
req=req,
|
||||
)
|
||||
project_id_value = str(project.id)
|
||||
step_id_value = str(step.id)
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"修改图片 AI 提词失败: {exc}")
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="图片 AI 提词已修改,后续步骤已软删除,请重新生成图片",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tasks/{project_id}/steps/{step_id}/video-prompt-schema",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="修改第4步视频 AI 提词 JSON schema",
|
||||
description=(
|
||||
"修改第4步视频 AI 提词 JSON schema,不调用 AI、不扣积分。"
|
||||
"前端提交的 schema 只作为 patch,服务端会锁定视频时长、比例、清晰度、帧率、推荐分辨率、"
|
||||
"动作/镜头/动态时间规划数组长度和时间段、输出规格、质量控制、合规控制、schema_version、schema_usage。"
|
||||
"最终提示词允许修改,但会清洗秒数、比例、分辨率、帧率等视频参数。保存后软删除第5步视频生成任务。"
|
||||
),
|
||||
)
|
||||
async def update_video_prompt_schema(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
step_id: str = Path(..., description="第4步视频 AI 提词子任务ID"),
|
||||
req: HotOpeningVideoPromptSchemaUpdateRequest = Body(..., description="视频 AI 提词 schema 修改参数"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
project, step = await update_hot_opening_video_prompt_schema(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
req=req,
|
||||
)
|
||||
project_id_value = str(project.id)
|
||||
step_id_value = str(step.id)
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"修改视频 AI 提词 schema 失败: {exc}")
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="视频 AI 提词 schema 已修改,第5步视频生成任务已软删除,请重新生成视频",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tasks/{project_id}/steps/{step_id}/generate-image-prompt",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="基于素材输入手动生成图片 AI 提词",
|
||||
description=(
|
||||
"基于第1步素材输入子任务手动生成第2步图片 AI 提词。"
|
||||
"如果已存在旧的第2、3、4、5步,会先软删除旧步骤,再创建新的第2步。"
|
||||
),
|
||||
)
|
||||
async def generate_image_prompt(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
step_id: str = Path(..., description="第1步素材输入子任务ID"),
|
||||
req: HotOpeningGenerateImagePromptRequest = Body(default_factory=HotOpeningGenerateImagePromptRequest, description="图片提词生成参数,当前无需传参,额外字段会忽略"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
_ = req
|
||||
if celery_app is None:
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
project, step = await submit_image_prompt_optimize(db, current_user=current_user, project_id=project_id, material_step_id=step_id)
|
||||
project_id_value = str(project.id)
|
||||
step_id_value = str(step.id)
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"图片提词任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.hot_opening_replicate_tasks import start_image_prompt_optimize
|
||||
|
||||
try:
|
||||
start_image_prompt_optimize.delay(project_id_value, step_id_value)
|
||||
except Exception as exc:
|
||||
await _mark_dispatch_failed_and_raise(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
message=f"图片提词任务投递失败: {exc}",
|
||||
)
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="图片 AI 提词任务已提交",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tasks/{project_id}/steps/{step_id}/generate-image",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="基于图片 AI 提词生成新项目图片",
|
||||
description=(
|
||||
"基于第2步图片 AI 提词生成新项目图片。调用时传入图片生成引擎和图片生成参数。"
|
||||
"后端会创建第3步图片生成子任务,ChatGenerationTask 幂等键由后端按任务ID自动生成,不再使用前端幂等键。"
|
||||
"图片生成媒体积分在创建 ChatGenerationTask 时扣除,生成失败走媒体积分退款。"
|
||||
"如果已存在旧的第3、4、5步,会先软删除旧步骤,再创建新的第3步。"
|
||||
),
|
||||
)
|
||||
async def generate_image(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
step_id: str = Path(..., description="第2步图片 AI 提词子任务ID"),
|
||||
req: HotOpeningGenerateImageRequest = Body(..., description="图片生成引擎和参数"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if celery_app is None:
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
project, step = await generate_image_from_prompt(db, current_user=current_user, project_id=project_id, prompt_step_id=step_id, req=req)
|
||||
project_id_value = str(project.id)
|
||||
step_id_value = str(step.id)
|
||||
chat_task_id_value = step.chat_task_id
|
||||
if not chat_task_id_value:
|
||||
raise HTTPException(status_code=500, detail="图片生成任务创建失败:chat_task_id为空")
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"图片生成任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.generation_create_tasks import chatapi_create_generation_task
|
||||
|
||||
try:
|
||||
chatapi_create_generation_task.delay(chat_task_id_value)
|
||||
except Exception as exc:
|
||||
await _mark_dispatch_failed_and_raise(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
message=f"图片生成任务投递失败: {exc}",
|
||||
)
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="图片生成任务已提交",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tasks/{project_id}/steps/{step_id}/generate-video-prompt",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="基于图片结果手动生成视频 AI 提词",
|
||||
description=(
|
||||
"基于第3步图片生成子任务手动生成第4步视频 AI 提词 JSON schema。"
|
||||
"视频时长、比例、分辨率集中在本步骤确定并写入 step.output_json.payload.params_used_for_prompt。"
|
||||
"视频 AI 提词成功后按文本 token 扣积分;该文本积分不参与后续视频生成失败退款。"
|
||||
"如果已存在旧的第4、5步,会先软删除旧步骤,再创建新的第4步。"
|
||||
),
|
||||
)
|
||||
async def generate_video_prompt(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
step_id: str = Path(..., description="第3步图片生成子任务ID"),
|
||||
req: HotOpeningGenerateVideoPromptRequest = Body(..., description="视频提词生成参数,用于读取接口配置并规划视频时长、比例、分辨率"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if celery_app is None:
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
project, step = await submit_video_prompt_optimize(db, current_user=current_user, project_id=project_id, image_step_id=step_id, req=req)
|
||||
project_id_value = str(project.id)
|
||||
step_id_value = str(step.id)
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"视频提词任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.hot_opening_replicate_tasks import start_video_prompt_optimize
|
||||
|
||||
try:
|
||||
start_video_prompt_optimize.delay(project_id_value, step_id_value)
|
||||
except Exception as exc:
|
||||
await _mark_dispatch_failed_and_raise(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
message=f"视频提词任务投递失败: {exc}",
|
||||
)
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="视频 AI 提词任务已提交",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tasks/{project_id}/steps/{step_id}/generate-video",
|
||||
response_model=HotOpeningActionOut,
|
||||
summary="基于视频 AI 提词生成最终视频",
|
||||
description=(
|
||||
"基于第4步视频 AI 提词生成最终视频。请求体只需要选择视频生成引擎 engine_id。"
|
||||
"视频时长、比例、分辨率从第4步视频提词优化结果读取,不再由本接口动态传入。"
|
||||
"关联 ChatGenerationTask 的 original_prompt 和 optimized_prompt 都使用第4步生成的 prompt_schema JSON 字符串。"
|
||||
"ChatGenerationTask 幂等键由后端按任务ID自动生成;视频生成媒体积分失败时走退款。"
|
||||
"如果已存在旧的第5步,会先软删除旧步骤,再创建新的第5步。"
|
||||
),
|
||||
)
|
||||
async def generate_video(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
step_id: str = Path(..., description="第4步视频 AI 提词子任务ID"),
|
||||
req: HotOpeningGenerateVideoRequest = Body(..., description="视频生成参数:只传 engine_id,其它视频参数继承第4步视频提词结果"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if celery_app is None:
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
project, step = await generate_video_from_prompt(db, current_user=current_user, project_id=project_id, prompt_step_id=step_id, req=req)
|
||||
project_id_value = str(project.id)
|
||||
step_id_value = str(step.id)
|
||||
chat_task_id_value = step.chat_task_id
|
||||
if not chat_task_id_value:
|
||||
raise HTTPException(status_code=500, detail="视频生成任务创建失败:chat_task_id为空")
|
||||
await db.commit()
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"视频生成任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.generation_create_tasks import chatapi_create_generation_task
|
||||
|
||||
try:
|
||||
chatapi_create_generation_task.delay(chat_task_id_value)
|
||||
except Exception as exc:
|
||||
await _mark_dispatch_failed_and_raise(
|
||||
db,
|
||||
current_user=current_user,
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
message=f"视频生成任务投递失败: {exc}",
|
||||
)
|
||||
|
||||
return HotOpeningActionOut(
|
||||
message="视频生成任务已提交",
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
detail=await _reload_project_detail(db, current_user, project_id_value),
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/tasks/{project_id}",
|
||||
response_model=HotOpeningDeleteOut,
|
||||
summary="删除爆款开头复刻总任务项目",
|
||||
description="软删除爆款开头复刻总任务项目,并联动软删除当前有效子任务和关联的 ChatGenerationTask。",
|
||||
)
|
||||
async def delete_task(
|
||||
project_id: str = Path(..., description="总任务项目ID"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
result = await delete_hot_opening_project(db, current_user=current_user, project_id=project_id)
|
||||
await db.commit()
|
||||
return result
|
||||
except HTTPException:
|
||||
await db.rollback()
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"删除爆款开头复刻项目失败: {exc}")
|
||||
Reference in New Issue
Block a user