Implement active user tiers, class-based Tailwind theme switcher, 3-tiled static dashboard layout, Minio object storage integration, school & state settings database sync, and streak wishes onboarding modal
This commit is contained in:
91
backend/storage.py
Normal file
91
backend/storage.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import logging
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
from botocore.exceptions import ClientError
|
||||
import json
|
||||
|
||||
logger = logging.getLogger("storage")
|
||||
|
||||
# Read settings from environment
|
||||
MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "minio:9000")
|
||||
MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "minio_admin")
|
||||
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minio_secure_password_2026")
|
||||
MINIO_BUCKET = os.getenv("MINIO_BUCKET", "pragativeer-uploads")
|
||||
|
||||
# Initialize S3/Minio client
|
||||
# Since backend runs inside Docker, endpoint_url must point to 'http://minio:9000'
|
||||
# However, from the client browser, it will be reached via 'http://localhost:9000'
|
||||
s3_client = boto3.client(
|
||||
"s3",
|
||||
endpoint_url=f"http://{MINIO_ENDPOINT}",
|
||||
aws_access_key_id=MINIO_ACCESS_KEY,
|
||||
aws_secret_access_key=MINIO_SECRET_KEY,
|
||||
config=Config(signature_version="s3v4"),
|
||||
region_name="us-east-1"
|
||||
)
|
||||
|
||||
def ensure_bucket_exists():
|
||||
try:
|
||||
# Check if bucket exists
|
||||
s3_client.head_bucket(Bucket=MINIO_BUCKET)
|
||||
logger.info(f"Minio bucket '{MINIO_BUCKET}' already exists.")
|
||||
except ClientError as e:
|
||||
error_code = e.response["Error"]["Code"]
|
||||
if error_code == "404":
|
||||
try:
|
||||
# Create bucket
|
||||
s3_client.create_bucket(Bucket=MINIO_BUCKET)
|
||||
logger.info(f"Successfully created Minio bucket '{MINIO_BUCKET}'.")
|
||||
|
||||
# Set public read policy for direct browser access
|
||||
bucket_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "PublicReadGetObject",
|
||||
"Effect": "Allow",
|
||||
"Principal": "*",
|
||||
"Action": ["s3:GetObject"],
|
||||
"Resource": [f"arn:aws:s3:::{MINIO_BUCKET}/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
s3_client.put_bucket_policy(
|
||||
Bucket=MINIO_BUCKET,
|
||||
Policy=json.dumps(bucket_policy)
|
||||
)
|
||||
logger.info(f"Public read policy applied to Minio bucket '{MINIO_BUCKET}'.")
|
||||
except Exception as ex:
|
||||
logger.error(f"Failed to create Minio bucket: {ex}")
|
||||
else:
|
||||
logger.error(f"Error checking Minio bucket: {e}")
|
||||
|
||||
def upload_file(file_content: bytes, object_name: str, content_type: str = "application/octet-stream") -> str:
|
||||
"""Uploads file content to Minio and returns the direct public URL."""
|
||||
try:
|
||||
ensure_bucket_exists()
|
||||
s3_client.put_object(
|
||||
Bucket=MINIO_BUCKET,
|
||||
Key=object_name,
|
||||
Body=file_content,
|
||||
ContentType=content_type
|
||||
)
|
||||
|
||||
# Construct public URL (from browser's point of view, it connects to localhost:9000)
|
||||
# We can resolve this dynamically by returning a path like /api/uploads/ or direct localhost:9000 link
|
||||
public_url = f"http://localhost:9000/{MINIO_BUCKET}/{object_name}"
|
||||
logger.info(f"Successfully uploaded {object_name} to Minio. URL: {public_url}")
|
||||
return public_url
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upload file to Minio: {e}")
|
||||
return ""
|
||||
|
||||
def get_file(object_name: str) -> bytes:
|
||||
"""Downloads file content from Minio."""
|
||||
try:
|
||||
response = s3_client.get_object(Bucket=MINIO_BUCKET, Key=object_name)
|
||||
return response["Body"].read()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get file from Minio: {e}")
|
||||
return b""
|
||||
Reference in New Issue
Block a user