Files

187 lines
7.2 KiB
Python

from __future__ import annotations
from typing import Any
from fastapi import UploadFile
from sqlalchemy.ext.asyncio import AsyncSession
from app.enums.admin_upload import AdminUploadLogEventEnum, AdminUploadResourceTypeEnum, AdminUploadSceneEnum
from app.enums.common import LogEventStatusEnum, LogSourceEnum
from app.enums.upload_resource import (
UploadResourceBindStatusEnum,
UploadResourceCreatedByEnum,
UploadResourceDeletePolicyEnum,
UploadResourceDurationSourceEnum,
UploadResourceModuleEnum,
)
from app.models.user import User
from app.schemas.admin_upload import AdminUploadFileOut, AdminUploadMediaReferenceOut
from app.services.admin_upload.storage import save_upload_file
from app.services.operation_log_service import build_exception_detail, log_operation_event
from app.services.upload_resource.core_service import record_external_upload_resource
def _duration(value: float | None) -> float | None:
if value is None:
return None
try:
v = float(value)
return round(v, 3) if v >= 0 else None
except Exception:
return None
def _module_for_scene(scene: AdminUploadSceneEnum) -> str:
if scene == AdminUploadSceneEnum.HOME_MATERIAL_REFERENCE:
return UploadResourceModuleEnum.HOME_MATERIAL.value
return UploadResourceModuleEnum.ADMIN_UPLOAD.value
def _role_for_type(resource_type: AdminUploadResourceTypeEnum) -> str | None:
if resource_type == AdminUploadResourceTypeEnum.IMAGE:
return "reference_image"
if resource_type == AdminUploadResourceTypeEnum.VIDEO:
return "reference_video"
if resource_type == AdminUploadResourceTypeEnum.AUDIO:
return "reference_audio"
return None
def _media_reference(*, url: str, resource_type: AdminUploadResourceTypeEnum, name: str | None, duration_seconds: float | None, resource_id: str) -> AdminUploadMediaReferenceOut:
return AdminUploadMediaReferenceOut(
url=url,
type=resource_type.value,
name=name,
duration=duration_seconds,
source="admin_upload",
upload_resource_id=resource_id,
display_url=url,
preview_url=url,
role=_role_for_type(resource_type),
)
def _log(event_type: str, *, admin: User, scene: str, resource_type: str, status: str = LogEventStatusEnum.SUCCESS.value, message: str | None = None, detail: dict[str, Any] | None = None, error: str | None = None) -> None:
log_operation_event(
domain="admin_upload",
module="admin_upload",
event_type=event_type,
event_status=status,
source=LogSourceEnum.API.value,
user_id=admin.id,
message=message,
detail={"scene": scene, "resource_type": resource_type, **(detail or {})},
error=error,
)
class AdminUploadService:
async def upload_file(
self,
db: AsyncSession,
*,
file: UploadFile,
scene: AdminUploadSceneEnum,
resource_type: AdminUploadResourceTypeEnum,
admin: User,
duration_seconds: float | None = None,
) -> AdminUploadFileOut:
duration = _duration(duration_seconds)
_log(
AdminUploadLogEventEnum.VALIDATE_STARTED.value,
admin=admin,
scene=scene.value,
resource_type=resource_type.value,
status=LogEventStatusEnum.STARTED.value,
detail={"filename": file.filename, "content_type": file.content_type},
)
stored = None
try:
_log(
AdminUploadLogEventEnum.VALIDATE_SUCCESS.value,
admin=admin,
scene=scene.value,
resource_type=resource_type.value,
detail={"filename": file.filename, "duration_seconds": duration},
)
_log(
AdminUploadLogEventEnum.SAVE_STARTED.value,
admin=admin,
scene=scene.value,
resource_type=resource_type.value,
status=LogEventStatusEnum.STARTED.value,
detail={"filename": file.filename},
)
stored = await save_upload_file(file, scene=scene, resource_type=resource_type, admin_id=admin.id)
_log(
AdminUploadLogEventEnum.SAVE_SUCCESS.value,
admin=admin,
scene=scene.value,
resource_type=resource_type.value,
detail={"url": stored.file_url, "storage_path": stored.storage_path, "file_size_bytes": stored.file_size_bytes},
)
module = _module_for_scene(scene)
resource = await record_external_upload_resource(
db,
user_id=admin.id,
module=module,
resource_type=resource_type.value,
resource_url=stored.file_url,
storage_path=stored.storage_path,
file_size_bytes=stored.file_size_bytes,
file_name=stored.file_name,
mime_type=file.content_type,
duration_seconds=duration,
duration_source=UploadResourceDurationSourceEnum.CLIENT.value if duration is not None else None,
bind_status=UploadResourceBindStatusEnum.PENDING.value,
delete_policy=UploadResourceDeletePolicyEnum.USER_DELETABLE.value,
created_by=UploadResourceCreatedByEnum.API.value,
metadata={
"scene": scene.value,
"original_filename": stored.original_file_name,
"client_duration_seconds": duration_seconds,
"source": "admin_upload",
},
)
await db.refresh(resource)
_log(
AdminUploadLogEventEnum.DB_RECORD_SUCCESS.value,
admin=admin,
scene=scene.value,
resource_type=resource_type.value,
detail={"resource_id": resource.id, "url": stored.file_url, "module": module},
)
media_reference = _media_reference(
url=stored.file_url,
resource_type=resource_type,
name=stored.original_file_name or stored.file_name,
duration_seconds=resource.duration_seconds,
resource_id=resource.id,
)
return AdminUploadFileOut(
resource_id=resource.id,
scene=scene,
module=module,
resource_type=resource_type,
url=stored.file_url,
file_name=stored.file_name,
original_file_name=stored.original_file_name,
file_size_bytes=stored.file_size_bytes,
duration_seconds=resource.duration_seconds,
media_reference=media_reference,
)
except Exception as exc:
_log(
AdminUploadLogEventEnum.FAILED.value,
admin=admin,
scene=scene.value,
resource_type=resource_type.value,
status=LogEventStatusEnum.FAILED.value,
detail=build_exception_detail(exc, {"filename": file.filename}),
error=str(exc),
)
raise
admin_upload_service = AdminUploadService()