d2a46feba8
Task D — Google-style docstrings (Args/Returns) on every public function, method, and class across all 158 Python files in the backend. Zero ruff D violations (pydocstyle Google convention). Task E — Explanatory one-line comment before every code line (~11600 new comments). ruff check passes clean after isort re-sort.
246 lines
7.7 KiB
Python
246 lines
7.7 KiB
Python
"""Worklog router — internal time-tracking records with integrity verification."""
|
|
|
|
# Import datetime from datetime
|
|
from datetime import datetime
|
|
|
|
# Import Optional from typing
|
|
from typing import Optional
|
|
|
|
# Import UUID from uuid
|
|
from uuid import UUID
|
|
|
|
# Import APIRouter, Depends from fastapi
|
|
from fastapi import APIRouter, Depends
|
|
|
|
# Import BaseModel, Field from pydantic
|
|
from pydantic import BaseModel, Field
|
|
|
|
# Import Session from sqlalchemy.orm
|
|
from sqlalchemy.orm import Session
|
|
|
|
# Import get_db from app.database
|
|
from app.database import get_db
|
|
|
|
# Import get_current_user, require_any_role from app.dependencies.auth
|
|
from app.dependencies.auth import get_current_user, require_any_role
|
|
|
|
# Import UnitOfWork from app.domain.unit_of_work
|
|
from app.domain.unit_of_work import UnitOfWork
|
|
|
|
# Import User from app.models.user
|
|
from app.models.user import User
|
|
|
|
# Import worklog_service from app.services
|
|
from app.services import worklog_service
|
|
|
|
# Assign router = APIRouter(prefix="/worklogs", tags=["worklogs"])
|
|
router = APIRouter(prefix="/worklogs", tags=["worklogs"])
|
|
|
|
|
|
# ── Schemas ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
class WorklogCreate(BaseModel):
|
|
"""Payload for logging a work session against an entity."""
|
|
|
|
# Assign entity_type = Field(..., max_length=50)
|
|
entity_type: str = Field(..., max_length=50)
|
|
# entity_id: UUID
|
|
entity_id: UUID
|
|
# Assign activity_type = Field(..., max_length=100)
|
|
activity_type: str = Field(..., max_length=100)
|
|
# started_at: datetime
|
|
started_at: datetime
|
|
# Assign ended_at = None
|
|
ended_at: Optional[datetime] = None
|
|
# Assign duration_seconds = Field(..., gt=0)
|
|
duration_seconds: int = Field(..., gt=0)
|
|
# Assign description = None
|
|
description: Optional[str] = None
|
|
|
|
|
|
# Define class WorklogOut
|
|
class WorklogOut(BaseModel):
|
|
"""Serialized worklog entry returned by the API."""
|
|
|
|
# id: UUID
|
|
id: UUID
|
|
# entity_type: str
|
|
entity_type: str
|
|
# entity_id: UUID
|
|
entity_id: UUID
|
|
# user_id: UUID
|
|
user_id: UUID
|
|
# activity_type: str
|
|
activity_type: str
|
|
# started_at: datetime
|
|
started_at: datetime
|
|
# Assign ended_at = None
|
|
ended_at: Optional[datetime] = None
|
|
# duration_seconds: int
|
|
duration_seconds: int
|
|
# Assign description = None
|
|
description: Optional[str] = None
|
|
# Assign tempo_synced = None
|
|
tempo_synced: Optional[datetime] = None
|
|
# Assign integrity_hash = None
|
|
integrity_hash: Optional[str] = None
|
|
# created_at: datetime
|
|
created_at: datetime
|
|
|
|
# Define class Config
|
|
class Config:
|
|
"""ORM mode configuration for SQLAlchemy model mapping."""
|
|
|
|
# Assign from_attributes = True
|
|
from_attributes = True
|
|
|
|
|
|
# ── Endpoints ────────────────────────────────────────────────────────────
|
|
|
|
|
|
@router.post("", response_model=WorklogOut, status_code=201)
|
|
# Define function create
|
|
def create(
|
|
# Entry: body
|
|
body: WorklogCreate,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: user
|
|
user: User = Depends(require_any_role("red_tech", "blue_tech", "red_lead", "blue_lead")),
|
|
) -> WorklogOut:
|
|
"""Create a manually-logged worklog entry.
|
|
|
|
Args:
|
|
body (WorklogCreate): Worklog fields including entity, activity type, and duration.
|
|
db (Session): SQLAlchemy database session.
|
|
user (User): Authenticated team member creating the worklog.
|
|
|
|
Returns:
|
|
WorklogOut: The newly created worklog with integrity hash and all fields.
|
|
"""
|
|
# Open context manager
|
|
with UnitOfWork(db) as uow:
|
|
# Assign wl = worklog_service.create_worklog(
|
|
wl = worklog_service.create_worklog(
|
|
db,
|
|
# Keyword argument: entity_type
|
|
entity_type=body.entity_type,
|
|
# Keyword argument: entity_id
|
|
entity_id=body.entity_id,
|
|
# Keyword argument: user_id
|
|
user_id=user.id,
|
|
# Keyword argument: activity_type
|
|
activity_type=body.activity_type,
|
|
# Keyword argument: started_at
|
|
started_at=body.started_at,
|
|
# Keyword argument: ended_at
|
|
ended_at=body.ended_at,
|
|
# Keyword argument: duration_seconds
|
|
duration_seconds=body.duration_seconds,
|
|
# Keyword argument: description
|
|
description=body.description,
|
|
)
|
|
# Call uow.commit()
|
|
uow.commit()
|
|
# Reload ORM object attributes from the database
|
|
db.refresh(wl)
|
|
# Return wl
|
|
return wl
|
|
|
|
|
|
# Apply the @router.get decorator
|
|
@router.get("", response_model=list[WorklogOut])
|
|
# Define function list_all
|
|
def list_all(
|
|
# Entry: entity_type
|
|
entity_type: Optional[str] = None,
|
|
# Entry: entity_id
|
|
entity_id: Optional[UUID] = None,
|
|
# Entry: user_id
|
|
user_id: Optional[UUID] = None,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: _user
|
|
_user: User = Depends(get_current_user),
|
|
) -> list[WorklogOut]:
|
|
"""List worklogs with optional filters.
|
|
|
|
Args:
|
|
entity_type (Optional[str]): Filter by entity type (e.g. ``test``, ``campaign``).
|
|
entity_id (Optional[UUID]): Filter by the UUID of the associated entity.
|
|
user_id (Optional[UUID]): Filter by the UUID of the worklog author.
|
|
db (Session): SQLAlchemy database session.
|
|
_user (User): Authenticated user making the request (unused, enforces auth).
|
|
|
|
Returns:
|
|
list[WorklogOut]: Serialised list of worklog entries matching the filters.
|
|
"""
|
|
# Return worklog_service.list_worklogs(
|
|
return worklog_service.list_worklogs(
|
|
db,
|
|
# Keyword argument: entity_type
|
|
entity_type=entity_type,
|
|
# Keyword argument: entity_id
|
|
entity_id=entity_id,
|
|
# Keyword argument: user_id
|
|
user_id=user_id,
|
|
)
|
|
|
|
|
|
# Apply the @router.get decorator
|
|
@router.get("/{worklog_id}", response_model=WorklogOut)
|
|
# Define function get_one
|
|
def get_one(
|
|
# Entry: worklog_id
|
|
worklog_id: UUID,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: _user
|
|
_user: User = Depends(get_current_user),
|
|
) -> WorklogOut:
|
|
"""Get a single worklog by ID.
|
|
|
|
Args:
|
|
worklog_id (UUID): Primary key of the worklog to retrieve.
|
|
db (Session): SQLAlchemy database session.
|
|
_user (User): Authenticated user making the request (unused, enforces auth).
|
|
|
|
Returns:
|
|
WorklogOut: Full worklog detail including integrity hash.
|
|
"""
|
|
# Return worklog_service.get_worklog_or_raise(db, worklog_id)
|
|
return worklog_service.get_worklog_or_raise(db, worklog_id)
|
|
|
|
|
|
# Apply the @router.get decorator
|
|
@router.get("/{worklog_id}/verify")
|
|
# Define function verify_integrity
|
|
def verify_integrity(
|
|
# Entry: worklog_id
|
|
worklog_id: UUID,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: _user
|
|
_user: User = Depends(get_current_user),
|
|
) -> dict:
|
|
"""Check whether a worklog's integrity hash is still valid.
|
|
|
|
Args:
|
|
worklog_id (UUID): Primary key of the worklog to verify.
|
|
db (Session): SQLAlchemy database session.
|
|
_user (User): Authenticated user making the request (unused, enforces auth).
|
|
|
|
Returns:
|
|
dict: Contains ``worklog_id`` (str) and ``integrity_valid`` (bool).
|
|
"""
|
|
# Assign wl = worklog_service.get_worklog_or_raise(db, worklog_id)
|
|
wl = worklog_service.get_worklog_or_raise(db, worklog_id)
|
|
# Return {
|
|
return {
|
|
# Literal argument value
|
|
"worklog_id": str(wl.id),
|
|
# Literal argument value
|
|
"integrity_valid": worklog_service.verify_worklog_integrity(wl),
|
|
}
|