refactor: split monolithic main.py into FastAPI routers (v0.2.0)
- app/api/deps.py: shared auth dependencies - app/api/routers/auth.py: login, me - app/api/routers/issues.py: CRUD, transition, assign, relations, tags, batch, search - app/api/routers/projects.py: CRUD, members, worklog summary - app/api/routers/users.py: CRUD, worklogs - app/api/routers/comments.py: CRUD - app/api/routers/webhooks.py: CRUD, logs, retry - app/api/routers/misc.py: API keys, activity, milestones, notifications, worklogs, export, dashboard - main.py: 1165 lines → 51 lines - Version bump to 0.2.0
This commit is contained in:
114
app/api/routers/projects.py
Normal file
114
app/api/routers/projects.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""Projects router."""
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import get_db
|
||||
from app.models import models
|
||||
from app.schemas import schemas
|
||||
|
||||
router = APIRouter(prefix="/projects", tags=["Projects"])
|
||||
|
||||
|
||||
@router.post("", response_model=schemas.ProjectResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_project(project: schemas.ProjectCreate, db: Session = Depends(get_db)):
|
||||
db_project = models.Project(**project.model_dump())
|
||||
db.add(db_project)
|
||||
db.commit()
|
||||
db.refresh(db_project)
|
||||
return db_project
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.ProjectResponse])
|
||||
def list_projects(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
return db.query(models.Project).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
@router.get("/{project_id}", response_model=schemas.ProjectResponse)
|
||||
def get_project(project_id: int, db: Session = Depends(get_db)):
|
||||
project = db.query(models.Project).filter(models.Project.id == project_id).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
return project
|
||||
|
||||
|
||||
@router.patch("/{project_id}", response_model=schemas.ProjectResponse)
|
||||
def update_project(project_id: int, project_update: schemas.ProjectUpdate, db: Session = Depends(get_db)):
|
||||
project = db.query(models.Project).filter(models.Project.id == project_id).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
for field, value in project_update.model_dump(exclude_unset=True).items():
|
||||
setattr(project, field, value)
|
||||
db.commit()
|
||||
db.refresh(project)
|
||||
return project
|
||||
|
||||
|
||||
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_project(project_id: int, db: Session = Depends(get_db)):
|
||||
project = db.query(models.Project).filter(models.Project.id == project_id).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
db.delete(project)
|
||||
db.commit()
|
||||
return None
|
||||
|
||||
|
||||
# ---- Members ----
|
||||
|
||||
@router.post("/{project_id}/members", response_model=schemas.ProjectMemberResponse, status_code=status.HTTP_201_CREATED)
|
||||
def add_project_member(project_id: int, member: schemas.ProjectMemberCreate, db: Session = Depends(get_db)):
|
||||
project = db.query(models.Project).filter(models.Project.id == project_id).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
user = db.query(models.User).filter(models.User.id == member.user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
existing = db.query(models.ProjectMember).filter(
|
||||
models.ProjectMember.project_id == project_id, models.ProjectMember.user_id == member.user_id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="User already a member")
|
||||
db_member = models.ProjectMember(project_id=project_id, user_id=member.user_id, role=member.role)
|
||||
db.add(db_member)
|
||||
db.commit()
|
||||
db.refresh(db_member)
|
||||
return db_member
|
||||
|
||||
|
||||
@router.get("/{project_id}/members", response_model=List[schemas.ProjectMemberResponse])
|
||||
def list_project_members(project_id: int, db: Session = Depends(get_db)):
|
||||
return db.query(models.ProjectMember).filter(models.ProjectMember.project_id == project_id).all()
|
||||
|
||||
|
||||
@router.delete("/{project_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def remove_project_member(project_id: int, user_id: int, db: Session = Depends(get_db)):
|
||||
member = db.query(models.ProjectMember).filter(
|
||||
models.ProjectMember.project_id == project_id, models.ProjectMember.user_id == user_id
|
||||
).first()
|
||||
if not member:
|
||||
raise HTTPException(status_code=404, detail="Member not found")
|
||||
db.delete(member)
|
||||
db.commit()
|
||||
return None
|
||||
|
||||
|
||||
# ---- Worklog summary ----
|
||||
|
||||
from app.models.worklog import WorkLog
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
|
||||
@router.get("/{project_id}/worklogs/summary")
|
||||
def project_worklog_summary(project_id: int, db: Session = Depends(get_db)):
|
||||
results = db.query(
|
||||
models.User.id, models.User.username,
|
||||
sqlfunc.sum(WorkLog.hours).label("total_hours"),
|
||||
sqlfunc.count(WorkLog.id).label("log_count")
|
||||
).join(WorkLog, WorkLog.user_id == models.User.id)\
|
||||
.join(models.Issue, WorkLog.issue_id == models.Issue.id)\
|
||||
.filter(models.Issue.project_id == project_id)\
|
||||
.group_by(models.User.id, models.User.username).all()
|
||||
total = sum(r.total_hours for r in results)
|
||||
by_user = [{"user_id": r.id, "username": r.username, "hours": round(r.total_hours, 2), "logs": r.log_count} for r in results]
|
||||
return {"project_id": project_id, "total_hours": round(total, 2), "by_user": by_user}
|
||||
Reference in New Issue
Block a user