""" TemplateUploadService encapsulates validation, storage, and DB writes for template uploads to keep API endpoints thin and testable. """ from __future__ import annotations from typing import Optional import hashlib from fastapi import UploadFile from sqlalchemy.orm import Session from app.models.templates import DocumentTemplate, DocumentTemplateVersion from app.services.storage import get_default_storage from app.services.template_service import get_template_or_404 from app.services.template_search import TemplateSearchService class TemplateUploadService: """Service class for handling template uploads and initial version creation.""" def __init__(self, db: Session) -> None: self.db = db async def upload_template( self, *, name: str, category: Optional[str], description: Optional[str], semantic_version: str, file: UploadFile, created_by: Optional[str], ) -> DocumentTemplate: """Validate, store, and create a template with its first version.""" from app.utils.file_security import file_validator # Validate upload and sanitize metadata content, safe_filename, _file_ext, mime_type = await file_validator.validate_upload_file( file, category="template" ) checksum_sha256 = hashlib.sha256(content).hexdigest() storage = get_default_storage() storage_path = storage.save_bytes( content=content, filename_hint=safe_filename, subdir="templates", ) # Ensure unique template name by appending numeric suffix when duplicated base_name = name unique_name = base_name suffix = 2 while ( self.db.query(DocumentTemplate).filter(DocumentTemplate.name == unique_name).first() is not None ): unique_name = f"{base_name} ({suffix})" suffix += 1 # Create template row template = DocumentTemplate( name=unique_name, description=description, category=category, active=True, created_by=created_by, ) self.db.add(template) self.db.flush() # obtain template.id # Create initial version row version = DocumentTemplateVersion( template_id=template.id, semantic_version=semantic_version, storage_path=storage_path, mime_type=mime_type, size=len(content), checksum=checksum_sha256, changelog=None, created_by=created_by, is_approved=True, ) self.db.add(version) self.db.flush() # Point template to current approved version template.current_version_id = version.id # Persist and refresh self.db.commit() self.db.refresh(template) # Invalidate search caches after upload try: # Best-effort: this is async API; call via service helper import asyncio service = TemplateSearchService(self.db) if asyncio.get_event_loop().is_running(): asyncio.create_task(service.invalidate_all()) # type: ignore else: asyncio.run(service.invalidate_all()) # type: ignore except Exception: pass return template