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:
2026-07-07 13:43:30 +05:30
parent 426b98ddab
commit 04db17ea0e
24 changed files with 913 additions and 236 deletions

91
backend/storage.py Normal file
View 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""