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:
20
app.py
20
app.py
@@ -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)
|
||||||
|
|||||||
BIN
attached_assets/image_1765661887413.png
Normal file
BIN
attached_assets/image_1765661887413.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user