Files
LogHive/backend/app/api/projects.py
T
2026-05-09 14:55:14 +08:00

124 lines
3.8 KiB
Python

"""Project management API routes."""
import uuid
from datetime import datetime, timezone
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.models.project import Project
from app.schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse, ProjectWithToken
router = APIRouter(prefix="/api/projects", tags=["projects"])
@router.post("", response_model=ProjectWithToken, status_code=201)
async def create_project(
data: ProjectCreate,
db: AsyncSession = Depends(get_db),
):
"""Register a new project for log collection."""
existing = await db.execute(select(Project).where(Project.name == data.name))
if existing.scalar_one_or_none():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Project '{data.name}' already exists",
)
project = Project(
name=data.name,
description=data.description,
api_key=uuid.uuid4().hex,
)
db.add(project)
await db.flush()
await db.refresh(project)
return project
@router.get("", response_model=List[ProjectResponse])
async def list_projects(
db: AsyncSession = Depends(get_db),
):
"""List all registered projects."""
result = await db.execute(select(Project).order_by(Project.created_at.desc()))
return result.scalars().all()
@router.get("/{project_id}", response_model=ProjectResponse)
async def get_project(
project_id: str,
db: AsyncSession = Depends(get_db),
):
"""Get a single project by ID."""
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return project
@router.patch("/{project_id}", response_model=ProjectResponse)
async def update_project(
project_id: str,
data: ProjectUpdate,
db: AsyncSession = Depends(get_db),
):
"""Update a project."""
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
if data.name is not None:
# Check uniqueness
existing = await db.execute(
select(Project).where(Project.name == data.name, Project.id != project_id)
)
if existing.scalar_one_or_none():
raise HTTPException(status_code=409, detail="Name already taken")
project.name = data.name
if data.description is not None:
project.description = data.description
if data.is_active is not None:
project.is_active = data.is_active
project.updated_at = datetime.now(timezone.utc)
await db.flush()
await db.refresh(project)
return project
@router.delete("/{project_id}", status_code=204)
async def delete_project(
project_id: str,
db: AsyncSession = Depends(get_db),
):
"""Delete a project."""
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
await db.delete(project)
@router.post("/{project_id}/rotate-key", response_model=ProjectWithToken)
async def rotate_api_key(
project_id: str,
db: AsyncSession = Depends(get_db),
):
"""Rotate a project's API key."""
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
project.api_key = uuid.uuid4().hex
project.updated_at = datetime.now(timezone.utc)
await db.flush()
await db.refresh(project)
return project