Implement new file upload system using official SDK

Update object storage module to use the official Replit Object Storage SDK and add a route to serve files from storage.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: cd9a7d26-a4e5-4215-975c-c59f4ed1f06d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: db73fc59-d876-4299-b896-e021264939d1
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/d0a1d46d-d203-4308-bc6a-312ac7c0243b/cd9a7d26-a4e5-4215-975c-c59f4ed1f06d/05bPjFc
This commit is contained in:
abhiramtx
2025-12-13 21:39:31 +00:00
parent 4a0166b80e
commit 7f9612493c
3 changed files with 48 additions and 58 deletions

20
app.py
View File

@@ -417,5 +417,25 @@ def delete_sponsor():
flash('Sponsor deleted successfully', 'success') flash('Sponsor deleted successfully', 'success')
return redirect(url_for('admin_sponsors')) return redirect(url_for('admin_sponsors'))
@app.route('/storage/<path:filepath>')
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__': if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000) app.run(debug=True, host='0.0.0.0', port=5000)

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -1,64 +1,30 @@
""" """
Object Storage Service for FTC Team Website 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 from replit.object_storage import Client
import requests
from uuid import uuid4 from uuid import uuid4
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from datetime import datetime, timedelta import os
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}"
class ObjectStorageService: class ObjectStorageService:
def __init__(self): def __init__(self):
"""Initialize the object storage service""" """Initialize the Replit Object Storage client"""
self.bucket_name = os.environ.get('OBJECT_STORAGE_BUCKET') self.client = Client()
if not self.bucket_name: self.bucket_name = os.environ.get('OBJECT_STORAGE_BUCKET', 'default')
raise ValueError(
"OBJECT_STORAGE_BUCKET environment variable not set. "
"Please create a bucket in the Object Storage pane and set this variable."
)
def upload_file(self, file, folder='uploads'): def upload_file(self, file, folder='uploads'):
""" """
Upload a file to object storage using signed URLs Upload a file to object storage
Args: Args:
file: Werkzeug FileStorage object file: Werkzeug FileStorage object
folder: Folder name in the bucket (default: 'uploads') folder: Folder name (prefix) for organization
Returns: Returns:
str: Public URL of the uploaded file str: Path to the uploaded file for serving
""" """
if not file or not file.filename: if not file or not file.filename:
return None return None
@@ -68,22 +34,26 @@ class ObjectStorageService:
unique_filename = f"{uuid4()}{file_extension}" unique_filename = f"{uuid4()}{file_extension}"
object_name = f"{folder}/{unique_filename}" object_name = f"{folder}/{unique_filename}"
signed_url = get_signed_upload_url(self.bucket_name, object_name)
file_content = file.read() file_content = file.read()
content_type = file.content_type or 'application/octet-stream'
upload_response = requests.put( self.client.upload_from_bytes(object_name, file_content)
signed_url,
data=file_content,
headers={"Content-Type": content_type}
)
if not upload_response.ok: return f"/storage/{object_name}"
raise Exception(f"Upload failed: {upload_response.status_code} - {upload_response.text}")
return get_public_url(self.bucket_name, object_name) 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, url): def delete_file(self, path):
"""Delete a file from object storage (placeholder - not implemented)""" """Delete a file from object storage"""
try:
object_name = path.replace("/storage/", "")
self.client.delete(object_name)
return True
except:
return False return False
def list_files(self):
"""List all files in the bucket"""
return self.client.list()