diff --git a/attached_assets/image_1765661730127.png b/attached_assets/image_1765661730127.png new file mode 100644 index 0000000..1559b14 Binary files /dev/null and b/attached_assets/image_1765661730127.png differ diff --git a/attached_assets/image_1765661747621.png b/attached_assets/image_1765661747621.png new file mode 100644 index 0000000..d166ea0 Binary files /dev/null and b/attached_assets/image_1765661747621.png differ diff --git a/object_storage.py b/object_storage.py index 0a1d963..53277ea 100644 --- a/object_storage.py +++ b/object_storage.py @@ -1,60 +1,57 @@ """ Object Storage Service for FTC Team Website -Handles persistent file uploads using Google Cloud Storage (Replit Object Storage) +Handles persistent file uploads using Replit Object Storage (via sidecar signed URLs) """ -from google.cloud import storage -from google.auth import identity_pool import os -import json +import requests 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_replit_credentials(): - """Create proper credentials for Replit Object Storage""" - credentials_config = { - "audience": "replit", - "subject_token_type": "access_token", - "token_url": f"{REPLIT_SIDECAR_ENDPOINT}/token", - "type": "external_account", - "credential_source": { - "url": f"{REPLIT_SIDECAR_ENDPOINT}/credential", - "format": { - "type": "json", - "subject_token_field_name": "access_token", - }, - }, - "universe_domain": "googleapis.com", +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" } - credentials = identity_pool.Credentials.from_info(credentials_config) - return credentials + 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: def __init__(self): - """Initialize the object storage client with Replit credentials""" - credentials = get_replit_credentials() - self.client = storage.Client( - credentials=credentials, - project="", - ) - + """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." ) - - self.bucket = self.client.bucket(self.bucket_name) def upload_file(self, file, folder='uploads'): """ - Upload a file to object storage + Upload a file to object storage using signed URLs Args: file: Werkzeug FileStorage object @@ -71,28 +68,22 @@ class ObjectStorageService: unique_filename = f"{uuid4()}{file_extension}" object_name = f"{folder}/{unique_filename}" - blob = self.bucket.blob(object_name) - blob.upload_from_file( - file.stream, - content_type=file.content_type or 'application/octet-stream' + 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} ) - blob.make_public() + if not upload_response.ok: + raise Exception(f"Upload failed: {upload_response.status_code} - {upload_response.text}") - return blob.public_url - - def get_blob(self, url): - """Get a blob object from a URL""" - if not url or not url.startswith('https://storage.googleapis.com/'): - return None - - parts = url.replace(f'https://storage.googleapis.com/{self.bucket_name}/', '') - return self.bucket.blob(parts) + return get_public_url(self.bucket_name, object_name) def delete_file(self, url): - """Delete a file from object storage""" - blob = self.get_blob(url) - if blob and blob.exists(): - blob.delete() - return True + """Delete a file from object storage (placeholder - not implemented)""" return False diff --git a/static/images/dr5qp.jpg b/static/images/dr5qp.jpg new file mode 100644 index 0000000..fdd3963 Binary files /dev/null and b/static/images/dr5qp.jpg differ