from appwrite.client import Client from appwrite.services.storage import Storage from appwrite.input_file import InputFile from appwrite.exception import AppwriteException from fastapi import UploadFile, HTTPException from datetime import datetime, timedelta import uuid import os from typing import Dict, Any from ..config import settings from ..utils.validators import validate_file_extension, validate_file_size import logging class StorageService: def __init__(self): try: self.client = Client() self.client.set_endpoint(settings.APPWRITE_ENDPOINT) self.client.set_project(settings.APPWRITE_PROJECT_ID) self.client.set_key(settings.APPWRITE_API_KEY) self.storage = Storage(self.client) logging.info("StorageService initialized successfully") except Exception as e: logging.error(f"Failed to initialize storage service: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to initialize storage service: {str(e)}") def _get_expiration_seconds(self, expiration_time: str) -> int: expiration_map = { "30s": 30, "24h": 86400, "3d": 259200, "5d": 432000, "7d": 604800 } return expiration_map.get(expiration_time, 86400) async def upload_file(self, file: UploadFile, expiration_time: str) -> Dict[str, Any]: try: validate_file_extension(file.filename) contents = await file.read() validate_file_size(len(contents)) _, ext = os.path.splitext(file.filename) file_id = f"{uuid.uuid4().hex[:8]}{ext}" expiration_seconds = self._get_expiration_seconds(expiration_time) expiration = datetime.now() + timedelta(seconds=expiration_seconds) result = self.storage.create_file( bucket_id=settings.APPWRITE_BUCKET_ID, file_id=file_id, file=InputFile.from_bytes(contents, file.filename), permissions=['read("any")'] ) new_file_name = f"{file_id}__exp_{expiration.isoformat()}" self.storage.update_file( bucket_id=settings.APPWRITE_BUCKET_ID, file_id=file_id, name=new_file_name, permissions=['read("any")'] ) url = f"{settings.APPWRITE_ENDPOINT}/storage/buckets/{settings.APPWRITE_BUCKET_ID}/files/{file_id}/view?project={settings.APPWRITE_PROJECT_ID}" return { 'file_id': file_id, 'url': url, 'expiration': expiration.isoformat(), 'original_name': file.filename } except HTTPException as e: raise e except Exception as e: raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") finally: await file.seek(0) async def cleanup_expired_files(self): logging.info("Starting cleanup of expired files") try: files = self.storage.list_files(settings.APPWRITE_BUCKET_ID) logging.info(f"Found {len(files['files'])} files in the bucket") now = datetime.now() for file in files['files']: try: file_name = file['name'] file_id = file['$id'] logging.info(f"Processing file: {file_id} - {file_name}") if '__exp_' in file_name: _, exp_str = file_name.split('__exp_') expiration = datetime.fromisoformat(exp_str) logging.info(f"File expiration: {expiration}, Current time: {now}") if now > expiration: logging.info(f"Deleting expired file: {file_id}") self.storage.delete_file( bucket_id=settings.APPWRITE_BUCKET_ID, file_id=file_id ) logging.info(f"Successfully deleted expired file: {file_id}") else: logging.info(f"File {file_id} not yet expired") else: logging.warning(f"File {file_id} does not have expiration info in its name") except Exception as e: logging.error(f"Error processing file {file_id}: {str(e)}") continue logging.info("Cleanup process completed") except AppwriteException as e: logging.error(f"Appwrite error during cleanup: {str(e)}") except Exception as e: logging.error(f"Unexpected error during cleanup: {str(e)}")