Add persistent file storage and improve error handling
Refactor object storage initialization to use a dedicated function and add error handling for uploads, including a fallback to local storage. Replit-Commit-Author: Agent Replit-Commit-Session-Id: cd9a7d26-a4e5-4215-975c-c59f4ed1f06d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 127466ff-4d39-4e2e-bc28-80d552851c25 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:
4
.replit
4
.replit
@@ -12,10 +12,6 @@ deploymentTarget = "cloudrun"
|
|||||||
localPort = 5000
|
localPort = 5000
|
||||||
externalPort = 80
|
externalPort = 80
|
||||||
|
|
||||||
[[ports]]
|
|
||||||
localPort = 37833
|
|
||||||
externalPort = 3000
|
|
||||||
|
|
||||||
[agent]
|
[agent]
|
||||||
expertMode = true
|
expertMode = true
|
||||||
|
|
||||||
|
|||||||
18
app.py
18
app.py
@@ -17,11 +17,16 @@ ADMIN_PASSWORD = 'techturb123'
|
|||||||
try:
|
try:
|
||||||
storage_service = ObjectStorageService()
|
storage_service = ObjectStorageService()
|
||||||
USING_OBJECT_STORAGE = True
|
USING_OBJECT_STORAGE = True
|
||||||
|
print(f"Object Storage initialized successfully - using bucket: {storage_service.bucket_name}")
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"Warning: Object Storage not configured - {e}")
|
print(f"Warning: Object Storage not configured - {e}")
|
||||||
print("Files will be saved locally (ephemeral storage)")
|
print("Files will be saved locally (ephemeral storage)")
|
||||||
storage_service = None
|
storage_service = None
|
||||||
USING_OBJECT_STORAGE = False
|
USING_OBJECT_STORAGE = False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error initializing Object Storage: {e}")
|
||||||
|
storage_service = None
|
||||||
|
USING_OBJECT_STORAGE = False
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
conn = psycopg2.connect(os.environ['DATABASE_URL'])
|
conn = psycopg2.connect(os.environ['DATABASE_URL'])
|
||||||
@@ -36,10 +41,17 @@ def upload_file_to_storage(file, folder='uploads'):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if USING_OBJECT_STORAGE and storage_service:
|
if USING_OBJECT_STORAGE and storage_service:
|
||||||
# Upload to persistent object storage
|
try:
|
||||||
return storage_service.upload_file(file, folder)
|
result = storage_service.upload_file(file, folder)
|
||||||
|
print(f"File uploaded to object storage: {result}")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error uploading to object storage: {e}")
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
|
file.save(filepath)
|
||||||
|
return f'images/{filename}'
|
||||||
else:
|
else:
|
||||||
# Fallback to local storage (ephemeral)
|
|
||||||
filename = secure_filename(file.filename)
|
filename = secure_filename(file.filename)
|
||||||
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
file.save(filepath)
|
file.save(filepath)
|
||||||
|
|||||||
@@ -4,35 +4,45 @@ Handles persistent file uploads using Google Cloud Storage (Replit Object Storag
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from google.cloud import storage
|
from google.cloud import storage
|
||||||
|
from google.auth import identity_pool
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
REPLIT_SIDECAR_ENDPOINT = "http://127.0.0.1:1106"
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials = identity_pool.Credentials.from_info(credentials_config)
|
||||||
|
return credentials
|
||||||
|
|
||||||
|
|
||||||
class ObjectStorageService:
|
class ObjectStorageService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the object storage client with Replit credentials"""
|
"""Initialize the object storage client with Replit credentials"""
|
||||||
|
credentials = get_replit_credentials()
|
||||||
self.client = storage.Client(
|
self.client = storage.Client(
|
||||||
credentials={
|
credentials=credentials,
|
||||||
"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",
|
|
||||||
},
|
|
||||||
project="",
|
project="",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get bucket name from environment variable
|
|
||||||
self.bucket_name = os.environ.get('OBJECT_STORAGE_BUCKET')
|
self.bucket_name = os.environ.get('OBJECT_STORAGE_BUCKET')
|
||||||
if not self.bucket_name:
|
if not self.bucket_name:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -56,23 +66,19 @@ class ObjectStorageService:
|
|||||||
if not file or not file.filename:
|
if not file or not file.filename:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Generate unique filename
|
|
||||||
original_filename = secure_filename(file.filename)
|
original_filename = secure_filename(file.filename)
|
||||||
file_extension = os.path.splitext(original_filename)[1]
|
file_extension = os.path.splitext(original_filename)[1]
|
||||||
unique_filename = f"{uuid4()}{file_extension}"
|
unique_filename = f"{uuid4()}{file_extension}"
|
||||||
object_name = f"{folder}/{unique_filename}"
|
object_name = f"{folder}/{unique_filename}"
|
||||||
|
|
||||||
# Upload to bucket
|
|
||||||
blob = self.bucket.blob(object_name)
|
blob = self.bucket.blob(object_name)
|
||||||
blob.upload_from_file(
|
blob.upload_from_file(
|
||||||
file.stream,
|
file.stream,
|
||||||
content_type=file.content_type or 'application/octet-stream'
|
content_type=file.content_type or 'application/octet-stream'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make the blob publicly accessible
|
|
||||||
blob.make_public()
|
blob.make_public()
|
||||||
|
|
||||||
# Return the public URL
|
|
||||||
return blob.public_url
|
return blob.public_url
|
||||||
|
|
||||||
def get_blob(self, url):
|
def get_blob(self, url):
|
||||||
@@ -80,7 +86,6 @@ class ObjectStorageService:
|
|||||||
if not url or not url.startswith('https://storage.googleapis.com/'):
|
if not url or not url.startswith('https://storage.googleapis.com/'):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Extract blob name from URL
|
|
||||||
parts = url.replace(f'https://storage.googleapis.com/{self.bucket_name}/', '')
|
parts = url.replace(f'https://storage.googleapis.com/{self.bucket_name}/', '')
|
||||||
return self.bucket.blob(parts)
|
return self.bucket.blob(parts)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user