fix(api): make POST /projects atomic + 409 on conflicts

This commit is contained in:
Abhimanyu Saharan
2026-02-02 13:54:07 +05:30
parent 8f9c312fa3
commit dffcd34a35

View File

@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select from sqlmodel import Session, select
from app.api.utils import log_activity, get_actor_employee_id from app.api.utils import log_activity, get_actor_employee_id
@@ -17,16 +18,40 @@ def list_projects(session: Session = Depends(get_session)):
@router.post("", response_model=Project) @router.post("", response_model=Project)
def create_project(payload: ProjectCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): def create_project(
payload: ProjectCreate,
session: Session = Depends(get_session),
actor_employee_id: int = Depends(get_actor_employee_id),
):
"""Create a project.
Keep operation atomic: flush to get id, log activity, then commit once.
Translate DB integrity errors to 409s.
"""
proj = Project(**payload.model_dump()) proj = Project(**payload.model_dump())
session.add(proj) session.add(proj)
try:
session.flush()
log_activity(
session,
actor_employee_id=actor_employee_id,
entity_type="project",
entity_id=proj.id,
verb="created",
payload={"name": proj.name},
)
session.commit() session.commit()
except IntegrityError:
session.rollback()
raise HTTPException(status_code=409, detail="Project already exists or violates constraints")
session.refresh(proj) session.refresh(proj)
log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="created", payload={"name": proj.name})
session.commit()
return proj return proj
@router.patch("/{project_id}", response_model=Project) @router.patch("/{project_id}", response_model=Project)
def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
proj = session.get(Project, project_id) proj = session.get(Project, project_id)