This commit is contained in:
HotSwapp
2025-08-18 20:20:04 -05:00
parent 89b2bc0aa2
commit bac8cc4bd5
114 changed files with 30258 additions and 1341 deletions

View File

@@ -0,0 +1,110 @@
"""
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