diff --git a/app.py b/app.py index 0074c4b..bdfc9e4 100644 --- a/app.py +++ b/app.py @@ -417,5 +417,25 @@ def delete_sponsor(): flash('Sponsor deleted successfully', 'success') return redirect(url_for('admin_sponsors')) +@app.route('/storage/') +def serve_storage_file(filepath): + """Serve files from object storage""" + try: + if USING_OBJECT_STORAGE and storage_service: + file_bytes = storage_service.get_file_bytes(f"/storage/{filepath}") + ext = filepath.rsplit('.', 1)[-1].lower() if '.' in filepath else '' + content_types = { + 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', + 'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml' + } + content_type = content_types.get(ext, 'application/octet-stream') + from flask import Response + return Response(file_bytes, mimetype=content_type) + else: + return "Object storage not configured", 404 + except Exception as e: + print(f"Error serving file {filepath}: {e}") + return "File not found", 404 + if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/attached_assets/image_1765661887413.png b/attached_assets/image_1765661887413.png new file mode 100644 index 0000000..c3038a7 Binary files /dev/null and b/attached_assets/image_1765661887413.png differ diff --git a/object_storage.py b/object_storage.py index 53277ea..2963306 100644 --- a/object_storage.py +++ b/object_storage.py @@ -1,64 +1,30 @@ """ Object Storage Service for FTC Team Website -Handles persistent file uploads using Replit Object Storage (via sidecar signed URLs) +Uses official Replit Object Storage SDK """ -import os -import requests +from replit.object_storage import Client from uuid import uuid4 from werkzeug.utils import secure_filename -from datetime import datetime, timedelta - -REPLIT_SIDECAR_ENDPOINT = "http://127.0.0.1:1106" - - -def get_signed_upload_url(bucket_name, object_name, ttl_sec=900): - """Get a signed URL for uploading to object storage""" - request_data = { - "bucket_name": bucket_name, - "object_name": object_name, - "method": "PUT", - "expires_at": (datetime.utcnow() + timedelta(seconds=ttl_sec)).isoformat() + "Z" - } - - response = requests.post( - f"{REPLIT_SIDECAR_ENDPOINT}/object-storage/signed-object-url", - json=request_data, - headers={"Content-Type": "application/json"} - ) - - if not response.ok: - raise Exception(f"Failed to get signed URL: {response.status_code} - {response.text}") - - data = response.json() - return data.get("signed_url") - - -def get_public_url(bucket_name, object_name): - """Get the public URL for an object""" - return f"https://storage.googleapis.com/{bucket_name}/{object_name}" +import os class ObjectStorageService: def __init__(self): - """Initialize the object storage service""" - self.bucket_name = os.environ.get('OBJECT_STORAGE_BUCKET') - if not self.bucket_name: - raise ValueError( - "OBJECT_STORAGE_BUCKET environment variable not set. " - "Please create a bucket in the Object Storage pane and set this variable." - ) + """Initialize the Replit Object Storage client""" + self.client = Client() + self.bucket_name = os.environ.get('OBJECT_STORAGE_BUCKET', 'default') def upload_file(self, file, folder='uploads'): """ - Upload a file to object storage using signed URLs + Upload a file to object storage Args: file: Werkzeug FileStorage object - folder: Folder name in the bucket (default: 'uploads') + folder: Folder name (prefix) for organization Returns: - str: Public URL of the uploaded file + str: Path to the uploaded file for serving """ if not file or not file.filename: return None @@ -68,22 +34,26 @@ class ObjectStorageService: unique_filename = f"{uuid4()}{file_extension}" object_name = f"{folder}/{unique_filename}" - signed_url = get_signed_upload_url(self.bucket_name, object_name) - file_content = file.read() - content_type = file.content_type or 'application/octet-stream' - upload_response = requests.put( - signed_url, - data=file_content, - headers={"Content-Type": content_type} - ) + self.client.upload_from_bytes(object_name, file_content) - if not upload_response.ok: - raise Exception(f"Upload failed: {upload_response.status_code} - {upload_response.text}") - - return get_public_url(self.bucket_name, object_name) + return f"/storage/{object_name}" - def delete_file(self, url): - """Delete a file from object storage (placeholder - not implemented)""" - return False + def get_file_bytes(self, path): + """Download a file as bytes""" + object_name = path.replace("/storage/", "") + return self.client.download_as_bytes(object_name) + + def delete_file(self, path): + """Delete a file from object storage""" + try: + object_name = path.replace("/storage/", "") + self.client.delete(object_name) + return True + except: + return False + + def list_files(self): + """List all files in the bucket""" + return self.client.list()