changes
This commit is contained in:
110
app/services/template_upload.py
Normal file
110
app/services/template_upload.py
Normal 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user