import os import uuid from typing import Optional import aiofiles from fastapi import UploadFile, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from starlette.responses import FileResponse from werkzeug.utils import secure_filename from app.application.appointment_files_repository import AppointmentFilesRepository from app.application.appointments_repository import AppointmentsRepository from app.domain.entities.appointment_file import AppointmentFileEntity from app.domain.models import AppointmentFile, User class AppointmentFilesService: def __init__(self, db: AsyncSession): self.appointment_files_repository = AppointmentFilesRepository(db) self.appointments_repository = AppointmentsRepository(db) async def get_file_by_id(self, file_id: int, user: User) -> FileResponse: appointment_file = await self.appointment_files_repository.get_by_id(file_id) if not appointment_file: raise HTTPException(404, "Файл с таким ID не найден") appointment = await self.appointments_repository.get_by_id(appointment_file.appointment_id) if not appointment: raise HTTPException(404, "Прием с таким ID не найден") if appointment.doctor_id != user.id and user.role.title != 'Администратор': raise HTTPException(403, 'Доступ запрещен') if not os.path.exists(appointment_file.file_path): raise HTTPException(404, "Файл не найден на диске") return FileResponse( appointment_file.file_path, media_type=self.get_media_type(appointment_file.file_path), filename=os.path.basename(appointment_file.file_title), ) async def get_files_by_appointment_id(self, appointment_id: int, user: User) -> Optional[ list[AppointmentFileEntity] ]: appointment = await self.appointments_repository.get_by_id(appointment_id) if not appointment: raise HTTPException(404, "Прием с таким ID не найден") if appointment.doctor_id != user.id and user.role.title != 'Администратор': raise HTTPException(403, 'Доступ запрещен') appointment_files = await self.appointment_files_repository.get_by_appointment_id(appointment_id) return [ self.model_to_entity(appointment_file) for appointment_file in appointment_files ] async def upload_file(self, appointment_id: int, file: UploadFile, user: User) -> AppointmentFileEntity: appointment = await self.appointments_repository.get_by_id(appointment_id) if not appointment: raise HTTPException(404, "Прием с таким ID не найден") if appointment.doctor_id != user.id and user.role.title != 'Администратор': raise HTTPException(403, 'Доступ запрещен') file_path = await self.save_file(file, f'uploads/appointment_files/{appointment.id}') appointment_file = AppointmentFile( file_title=file.filename, file_path=file_path, appointment_id=appointment_id, ) return self.model_to_entity( await self.appointment_files_repository.create(appointment_file) ) async def delete_file(self, file_id: int, user: User) -> AppointmentFileEntity: appointment_file = await self.appointment_files_repository.get_by_id(file_id) if not appointment_file: raise HTTPException(404, "Файл с таким ID не найден") appointment = await self.appointments_repository.get_by_id(appointment_file.appointment_id) if not appointment: raise HTTPException(404, "Прием с таким ID не найден") if appointment.doctor_id != user.id and user.role.title != 'Администратор': raise HTTPException(403, 'Доступ запрещен') if not os.path.exists(appointment_file.file_path): raise HTTPException(404, "Файл не найден на диске") if os.path.exists(appointment_file.file_path): os.remove(appointment_file.file_path) return self.model_to_entity( await self.appointment_files_repository.delete(appointment_file) ) async def save_file(self, file: UploadFile, upload_dir: str = 'uploads/appointment_files') -> str: os.makedirs(upload_dir, exist_ok=True) filename = self.generate_filename(file) file_path = os.path.join(upload_dir, filename) async with aiofiles.open(file_path, 'wb') as out_file: content = await file.read() await out_file.write(content) return file_path @staticmethod def generate_filename(file: UploadFile) -> str: return secure_filename(f"{uuid.uuid4()}_{file.filename}") @staticmethod def model_to_entity(appointment_file_model: AppointmentFile) -> AppointmentFileEntity: return AppointmentFileEntity( id=appointment_file_model.id, file_path=appointment_file_model.file_path, file_title=appointment_file_model.file_title, appointment_id=appointment_file_model.appointment_id, ) @staticmethod def get_media_type(filename: str) -> str: extension = filename.split('.')[-1].lower() if extension in ['jpeg', 'jpg', 'png']: return f"image/{extension}" if extension == 'pdf': return "application/pdf" if extension in ['zip']: return "application/zip" if extension in ['doc', 'docx']: return "application/msword" if extension in ['xls', 'xlsx']: return "application/vnd.ms-excel" if extension in ['ppt', 'pptx']: return "application/vnd.ms-powerpoint" if extension in ['txt']: return "text/plain" return "application/octet-stream"