Files
Aegis/backend/app/routers/worklogs.py
Kitos a4a2adccee
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
feat(phase-39): role-based access control overhaul + forced password change
- Add must_change_password field to User model with migration b023

- Add POST /auth/change-password endpoint with password policy validation

- Add require_password_changed dependency to block requests until password is changed

- Add ChangePasswordModal with live password policy checklist (forced on first login)

- Show password policy in CreateUserModal and EditUserModal

- Fix backend permissions: tests, campaigns, templates, reports, evidence, worklogs

- red_tech/blue_tech: execute only, cannot create tests/campaigns/templates

- red_lead/blue_lead: create/edit tests/campaigns/templates, generate reports, no system access

- viewer: read-only everywhere, can generate reports

- Fix frontend role checks across TestDetailPage, TestDetailHeader, TeamTabs, TestsPage, CampaignsPage, CampaignDetailPage, Sidebar
2026-02-18 10:37:02 +01:00

120 lines
3.6 KiB
Python

"""Worklog router — internal time-tracking records with integrity verification."""
from datetime import datetime
from typing import Optional
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.database import get_db
from app.dependencies.auth import get_current_user, require_any_role
from app.domain.exceptions import EntityNotFoundError
from app.models.user import User
from app.models.worklog import Worklog
from app.services import worklog_service
router = APIRouter(prefix="/worklogs", tags=["worklogs"])
# ── Schemas ──────────────────────────────────────────────────────────────
class WorklogCreate(BaseModel):
entity_type: str = Field(..., max_length=50)
entity_id: UUID
activity_type: str = Field(..., max_length=100)
started_at: datetime
ended_at: Optional[datetime] = None
duration_seconds: int = Field(..., gt=0)
description: Optional[str] = None
class WorklogOut(BaseModel):
id: UUID
entity_type: str
entity_id: UUID
user_id: UUID
activity_type: str
started_at: datetime
ended_at: Optional[datetime] = None
duration_seconds: int
description: Optional[str] = None
tempo_synced: Optional[datetime] = None
integrity_hash: Optional[str] = None
created_at: datetime
class Config:
from_attributes = True
# ── Endpoints ────────────────────────────────────────────────────────────
@router.post("", response_model=WorklogOut, status_code=201)
def create(
body: WorklogCreate,
db: Session = Depends(get_db),
user: User = Depends(require_any_role("red_tech", "blue_tech", "red_lead", "blue_lead")),
):
"""Create a manually-logged worklog entry."""
wl = worklog_service.create_worklog(
db,
entity_type=body.entity_type,
entity_id=body.entity_id,
user_id=user.id,
activity_type=body.activity_type,
started_at=body.started_at,
ended_at=body.ended_at,
duration_seconds=body.duration_seconds,
description=body.description,
)
return wl
@router.get("", response_model=list[WorklogOut])
def list_all(
entity_type: Optional[str] = None,
entity_id: Optional[UUID] = None,
user_id: Optional[UUID] = None,
db: Session = Depends(get_db),
_user: User = Depends(get_current_user),
):
"""List worklogs with optional filters."""
return worklog_service.list_worklogs(
db,
entity_type=entity_type,
entity_id=entity_id,
user_id=user_id,
)
@router.get("/{worklog_id}", response_model=WorklogOut)
def get_one(
worklog_id: UUID,
db: Session = Depends(get_db),
_user: User = Depends(get_current_user),
):
"""Get a single worklog by ID."""
wl = db.query(Worklog).filter(Worklog.id == worklog_id).first()
if not wl:
raise EntityNotFoundError("Worklog", str(worklog_id))
return wl
@router.get("/{worklog_id}/verify")
def verify_integrity(
worklog_id: UUID,
db: Session = Depends(get_db),
_user: User = Depends(get_current_user),
):
"""Check whether a worklog's integrity hash is still valid."""
wl = db.query(Worklog).filter(Worklog.id == worklog_id).first()
if not wl:
raise EntityNotFoundError("Worklog", str(worklog_id))
return {
"worklog_id": str(wl.id),
"integrity_valid": worklog_service.verify_worklog_integrity(wl),
}