Files
Aegis/backend/app/services/worklog_service.py

84 lines
2.3 KiB
Python

"""Internal worklog service — CRUD with integrity hashing."""
import hashlib
import logging
from datetime import datetime
from typing import Optional
from uuid import UUID
from sqlalchemy.orm import Session
from app.domain.errors import EntityNotFoundError
from app.models.worklog import Worklog
logger = logging.getLogger(__name__)
def create_worklog(
db: Session,
*,
entity_type: str,
entity_id: UUID,
user_id: UUID,
activity_type: str,
started_at: datetime,
duration_seconds: int,
ended_at: Optional[datetime] = None,
description: Optional[str] = None,
) -> Worklog:
"""Create a worklog with an auto-computed integrity hash."""
wl = Worklog(
entity_type=entity_type,
entity_id=entity_id,
user_id=user_id,
activity_type=activity_type,
started_at=started_at,
ended_at=ended_at,
duration_seconds=duration_seconds,
description=description,
)
wl.integrity_hash = _compute_hash(wl)
db.add(wl)
# Does not commit; caller (router) uses UnitOfWork.
return wl
def get_worklog_or_raise(db: Session, worklog_id: UUID) -> Worklog:
"""Get a worklog by ID or raise EntityNotFoundError."""
wl = db.query(Worklog).filter(Worklog.id == worklog_id).first()
if not wl:
raise EntityNotFoundError("Worklog", str(worklog_id))
return wl
def list_worklogs(
db: Session,
*,
entity_type: Optional[str] = None,
entity_id: Optional[UUID] = None,
user_id: Optional[UUID] = None,
) -> list[Worklog]:
"""List worklogs with optional filters."""
query = db.query(Worklog)
if entity_type:
query = query.filter(Worklog.entity_type == entity_type)
if entity_id:
query = query.filter(Worklog.entity_id == entity_id)
if user_id:
query = query.filter(Worklog.user_id == user_id)
return query.order_by(Worklog.started_at.desc()).all()
def verify_worklog_integrity(wl: Worklog) -> bool:
"""Return True if the worklog has not been tampered with."""
return wl.integrity_hash == _compute_hash(wl)
def _compute_hash(wl: Worklog) -> str:
"""SHA-256 of the immutable fields for audit integrity."""
data = (
f"{wl.entity_type}:{wl.entity_id}:{wl.user_id}:"
f"{wl.activity_type}:{wl.started_at}:{wl.duration_seconds}"
)
return hashlib.sha256(data.encode()).hexdigest()