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