- Auto-create/update OmgEntry when Project.job_number changes (PATCH /api/projects); delete stale entry on clear; sync name/client when those fields change too - Backfill script: scripts/backfill_omg_from_projects.py - Projects List-view: add OMG # column with link to /omg?highlight=<job_number>; Grid-view badge also made clickable; OmgView supports ?highlight= deep-link with scroll+highlight - AzureWorkItem: add omg_number column (migration 0009), extracted from fields_json[Custom.OMGDeliverableNumber] on sync; DevOps table shows OMG # column with CC-project link when matched; toolbar badge shows count of items without OMG # - Session no longer lost on F5: refresh_token moved to HttpOnly+SameSite=Lax cookie; authStore.init() restores session on app start; axios interceptor retries on 401 via cookie refresh before logging out; POST /api/auth/logout clears cookie Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.auth import CurrentUser
|
|
from src.database import get_db
|
|
from src.models import OmgEntry, Project
|
|
from src.schemas import ProjectOut
|
|
|
|
router = APIRouter(prefix="/api/projects", tags=["projects"])
|
|
|
|
|
|
@router.get("", response_model=list[ProjectOut])
|
|
async def list_projects(user: CurrentUser, db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(
|
|
select(Project).where(Project.user_id == user.id).order_by(Project.display_name)
|
|
)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.patch("/{project_id}", response_model=ProjectOut)
|
|
async def update_project(
|
|
project_id: str,
|
|
body: dict,
|
|
user: CurrentUser,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
project = await db.get(Project, project_id)
|
|
if not project or project.user_id != user.id:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
old_job_number = project.job_number
|
|
|
|
if "display_name" in body:
|
|
project.display_name = str(body["display_name"])[:255]
|
|
if "client" in body:
|
|
project.client = str(body["client"])[:255]
|
|
if "job_number" in body:
|
|
project.job_number = str(body["job_number"])[:100]
|
|
if "repo_url" in body:
|
|
project.repo_url = str(body["repo_url"])[:500]
|
|
|
|
await _sync_omg_entry(user.id, project, old_job_number, body, db)
|
|
|
|
await db.commit()
|
|
await db.refresh(project)
|
|
return project
|
|
|
|
|
|
async def _sync_omg_entry(
|
|
user_id: str,
|
|
project: Project,
|
|
old_job_number: str,
|
|
changed_fields: dict,
|
|
db: AsyncSession,
|
|
) -> None:
|
|
"""Keep omg_entries in sync when project job_number / name / client changes."""
|
|
job_changed = "job_number" in changed_fields
|
|
meta_changed = "display_name" in changed_fields or "client" in changed_fields
|
|
|
|
if job_changed and old_job_number != project.job_number:
|
|
# Remove stale entry for the old number
|
|
if old_job_number:
|
|
res = await db.execute(
|
|
select(OmgEntry).where(
|
|
OmgEntry.user_id == user_id,
|
|
OmgEntry.job_number == old_job_number,
|
|
)
|
|
)
|
|
old_entry = res.scalar_one_or_none()
|
|
if old_entry:
|
|
await db.delete(old_entry)
|
|
|
|
if project.job_number:
|
|
await _upsert_omg(user_id, project, db)
|
|
|
|
elif meta_changed and project.job_number:
|
|
# Name or client changed but job_number stayed — update existing entry if any
|
|
await _upsert_omg(user_id, project, db)
|
|
|
|
|
|
async def _upsert_omg(user_id: str, project: Project, db: AsyncSession) -> None:
|
|
res = await db.execute(
|
|
select(OmgEntry).where(
|
|
OmgEntry.user_id == user_id,
|
|
OmgEntry.job_number == project.job_number,
|
|
)
|
|
)
|
|
entry = res.scalar_one_or_none()
|
|
if entry:
|
|
entry.name = project.display_name
|
|
entry.client = project.client
|
|
else:
|
|
db.add(OmgEntry(
|
|
user_id=user_id,
|
|
name=project.display_name,
|
|
client=project.client,
|
|
job_number=project.job_number,
|
|
))
|