111 lines
3.3 KiB
Python
111 lines
3.3 KiB
Python
"""
|
|
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
|
|
|
|
|