from typing import Optional from fastapi import APIRouter, Depends, Query, HTTPException, status from sqlalchemy.orm import Session from app.core.database import get_db from app.models.user import User from app.schemas.common import ResponseModel, ListResponse from app.services import project_service from app.api.deps import get_current_user, require_admin, require_manager, _is_admin router = APIRouter() @router.get("") def list_projects( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=500), keyword: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(require_manager), ): # Data isolation: non-admin users only see their own projects created_by = None if _is_admin(current_user) else current_user.id items, total = project_service.list_projects(db, keyword=keyword, page=page, page_size=page_size, created_by=created_by) data = [] for p in items: stats = project_service.get_project_stats(db, p.id) data.append({ "id": p.id, "name": p.name, "template_id": p.template_id, "status": p.status, "planned_start": p.planned_start.isoformat() if p.planned_start else None, "planned_end": p.planned_end.isoformat() if p.planned_end else None, "created_at": p.created_at.isoformat() if p.created_at else None, "stats": stats, }) return ListResponse(data=data, total=total, page=page, page_size=page_size) @router.post("") def create_project( name: str, template_id: int, target_source_ids: Optional[str] = None, description: Optional[str] = None, db: Session = Depends(get_db), current_user: User = Depends(require_manager), ): item = project_service.create_project( db, name=name, template_id=template_id, created_by=current_user.id, target_source_ids=target_source_ids, description=description, ) return ResponseModel(data={"id": item.id, "name": item.name}) @router.get("/{project_id}") def get_project( project_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): p = project_service.get_project(db, project_id) if not p: from fastapi import HTTPException, status raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在") stats = project_service.get_project_stats(db, p.id) return ResponseModel(data={ "id": p.id, "name": p.name, "template_id": p.template_id, "status": p.status, "description": p.description, "target_source_ids": p.target_source_ids, "planned_start": p.planned_start.isoformat() if p.planned_start else None, "planned_end": p.planned_end.isoformat() if p.planned_end else None, "created_at": p.created_at.isoformat() if p.created_at else None, "stats": stats, }) @router.delete("/{project_id}") def delete_project( project_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): p = project_service.get_project(db, project_id) if not p: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在") # Only admin or project creator can delete if not _is_admin(current_user) and p.created_by != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无权删除此项目") project_service.delete_project(db, project_id) return ResponseModel(message="删除成功") @router.post("/{project_id}/auto-classify") def project_auto_classify( project_id: int, background: bool = True, db: Session = Depends(get_db), current_user: User = Depends(require_manager), ): from app.tasks.classification_tasks import auto_classify_task from celery.result import AsyncResult project = project_service.get_project(db, project_id) if not project: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在") try: if background: # Check if already running if project.celery_task_id: existing = AsyncResult(project.celery_task_id) if existing.state in ("PENDING", "PROGRESS", "STARTED"): return ResponseModel(data={"task_id": project.celery_task_id, "status": existing.state}) task = auto_classify_task.delay(project_id) project.celery_task_id = task.id project.status = "scanning" db.commit() return ResponseModel(data={"task_id": task.id, "status": task.state}) else: from app.services.classification_engine import run_auto_classification project.status = "scanning" db.commit() result = run_auto_classification(db, project_id) if result.get("success"): project.status = "assigning" else: project.status = "created" db.commit() return ResponseModel(data=result) except Exception as e: import logging logging.exception("Auto classify failed") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"自动分类执行失败: {str(e)}" ) @router.get("/{project_id}/auto-classify-status") def project_auto_classify_status( project_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): from celery.result import AsyncResult import json project = project_service.get_project(db, project_id) if not project: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在") task_id = project.celery_task_id if not task_id: # Return persisted progress if any progress = json.loads(project.scan_progress) if project.scan_progress else None return ResponseModel(data={"status": project.status, "progress": progress}) result = AsyncResult(task_id) progress = None if result.state == "PROGRESS" and result.info: progress = result.info elif project.scan_progress: progress = json.loads(project.scan_progress) return ResponseModel(data={ "status": result.state, "task_id": task_id, "progress": progress, "project_status": project.status, })