2026-03-17 14:36:31 +08:00
|
|
|
"""
|
|
|
|
|
Projects API Router
|
|
|
|
|
"""
|
2026-03-17 17:29:58 +08:00
|
|
|
import logging
|
2026-03-18 16:08:08 +08:00
|
|
|
import shutil
|
|
|
|
|
from pathlib import Path
|
2026-03-17 17:29:58 +08:00
|
|
|
from typing import List, Optional
|
2026-03-17 14:36:31 +08:00
|
|
|
from uuid import UUID
|
2026-03-17 17:29:58 +08:00
|
|
|
from fastapi import APIRouter, Depends, Query
|
2026-03-17 14:36:31 +08:00
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
2026-03-17 17:29:58 +08:00
|
|
|
|
|
|
|
|
from app.api.response import ApiResponse, PaginatedResponse
|
2026-03-17 14:36:31 +08:00
|
|
|
from app.core.database import get_db
|
2026-03-17 17:29:58 +08:00
|
|
|
from app.core.exceptions import NotFoundException
|
|
|
|
|
from app.core.crud import CRUDBase
|
2026-03-17 14:36:31 +08:00
|
|
|
from app.models.models import Project
|
2026-03-17 17:29:58 +08:00
|
|
|
from app.schemas.project import (
|
2026-03-17 14:36:31 +08:00
|
|
|
ProjectCreate,
|
|
|
|
|
ProjectUpdate,
|
2026-03-17 17:29:58 +08:00
|
|
|
ProjectResponse,
|
|
|
|
|
ProjectCreateSchema,
|
|
|
|
|
ProjectUpdateSchema
|
2026-03-17 14:36:31 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
2026-03-17 17:29:58 +08:00
|
|
|
logger = logging.getLogger("yg_dataset.projects")
|
|
|
|
|
|
|
|
|
|
# Initialize CRUD
|
|
|
|
|
project_crud = CRUDBase(Project)
|
|
|
|
|
|
2026-03-17 14:36:31 +08:00
|
|
|
|
2026-03-18 10:45:32 +08:00
|
|
|
@router.get("", response_model=PaginatedResponse)
|
2026-03-17 17:29:58 +08:00
|
|
|
async def list_projects(
|
|
|
|
|
page: int = Query(1, ge=1, description="Page number"),
|
|
|
|
|
page_size: int = Query(20, ge=1, le=100, description="Page size"),
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""List all projects with pagination"""
|
|
|
|
|
logger.info(f"Listing projects - page: {page}, page_size: {page_size}")
|
|
|
|
|
skip = (page - 1) * page_size
|
|
|
|
|
projects, total = await project_crud.get_multi(
|
|
|
|
|
db,
|
|
|
|
|
skip=skip,
|
|
|
|
|
limit=page_size,
|
|
|
|
|
order_by="created_at",
|
|
|
|
|
descending=True
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
logger.info(f"Found {total} projects, returning {len(projects)} items")
|
|
|
|
|
project_responses = [ProjectResponse.model_validate(p) for p in projects]
|
|
|
|
|
return PaginatedResponse.ok(
|
|
|
|
|
items=project_responses,
|
|
|
|
|
page=page,
|
|
|
|
|
page_size=page_size,
|
|
|
|
|
total=total
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
@router.post("", response_model=ApiResponse)
|
|
|
|
|
async def create_project(
|
|
|
|
|
project: ProjectCreateSchema,
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
):
|
2026-03-17 14:36:31 +08:00
|
|
|
"""Create a new project"""
|
2026-03-17 17:29:58 +08:00
|
|
|
logger.info(f"Creating project: name={project.name}, description={project.description}")
|
|
|
|
|
db_project = await project_crud.create(db, project)
|
|
|
|
|
logger.info(f"Project created successfully: id={db_project.id}")
|
|
|
|
|
return ApiResponse.ok(
|
|
|
|
|
data={"id": str(db_project.id)},
|
|
|
|
|
message="Project created successfully"
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
@router.get("/{project_id}", response_model=ApiResponse)
|
|
|
|
|
async def get_project(
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
):
|
2026-03-17 14:36:31 +08:00
|
|
|
"""Get project by ID"""
|
2026-03-17 17:29:58 +08:00
|
|
|
logger.info(f"Getting project: id={project_id}")
|
|
|
|
|
project = await project_crud.get_or_raise(db, project_id, "Project")
|
|
|
|
|
logger.info(f"Found project: name={project.name}")
|
|
|
|
|
return ApiResponse.ok(data=ProjectResponse.model_validate(project))
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
@router.put("/{project_id}", response_model=ApiResponse)
|
|
|
|
|
async def update_project(
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
project: ProjectUpdateSchema,
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
):
|
2026-03-17 14:36:31 +08:00
|
|
|
"""Update project"""
|
2026-03-17 17:29:58 +08:00
|
|
|
logger.info(f"Updating project: id={project_id}")
|
|
|
|
|
db_project = await project_crud.get_or_raise(db, project_id, "Project")
|
|
|
|
|
updated_project = await project_crud.update(db, db_project, project)
|
|
|
|
|
logger.info(f"Project updated: name={updated_project.name}")
|
|
|
|
|
return ApiResponse.ok(
|
|
|
|
|
data=ProjectResponse.model_validate(updated_project),
|
|
|
|
|
message="Project updated successfully"
|
|
|
|
|
)
|
2026-03-17 14:36:31 +08:00
|
|
|
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
@router.delete("/{project_id}", response_model=ApiResponse)
|
|
|
|
|
async def delete_project(
|
|
|
|
|
project_id: UUID,
|
|
|
|
|
db: AsyncSession = Depends(get_db)
|
|
|
|
|
):
|
2026-03-17 14:36:31 +08:00
|
|
|
"""Delete project"""
|
2026-03-17 17:29:58 +08:00
|
|
|
logger.info(f"Deleting project: id={project_id}")
|
|
|
|
|
await project_crud.get_or_raise(db, project_id, "Project")
|
|
|
|
|
await project_crud.delete(db, project_id)
|
2026-03-18 16:08:08 +08:00
|
|
|
|
|
|
|
|
# 删除项目对应的本地数据目录
|
|
|
|
|
project_data_dir = Path("/data/code/YG-Datasets/data") / str(project_id)
|
|
|
|
|
if project_data_dir.exists():
|
|
|
|
|
shutil.rmtree(project_data_dir)
|
|
|
|
|
logger.info(f"Project data directory deleted: {project_data_dir}")
|
|
|
|
|
|
2026-03-17 17:29:58 +08:00
|
|
|
logger.info(f"Project deleted: id={project_id}")
|
|
|
|
|
return ApiResponse.ok(message="Project deleted successfully")
|