Rewrite from Go + vanilla JS to Python (FastAPI) + React (TypeScript)

- Backend: Python 3.12 with FastAPI, SQLAlchemy, boto3
- Frontend: React 18 with TypeScript, Vite build tooling
- Updated Dockerfile for multi-stage Node + Python build
- Updated CI pipeline for Python backend
- Removed old Go code (cmd/, internal/, go.mod, go.sum)
- Updated README with new tech stack documentation
This commit is contained in:
Mondo Diaz
2025-12-05 17:16:43 -06:00
parent 343f7bfc59
commit 2261bfc830
45 changed files with 2104 additions and 3359 deletions

83
backend/app/storage.py Normal file
View File

@@ -0,0 +1,83 @@
import hashlib
from typing import BinaryIO, Tuple
import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
from .config import get_settings
settings = get_settings()
class S3Storage:
def __init__(self):
config = Config(s3={"addressing_style": "path"} if settings.s3_use_path_style else {})
self.client = boto3.client(
"s3",
endpoint_url=settings.s3_endpoint if settings.s3_endpoint else None,
region_name=settings.s3_region,
aws_access_key_id=settings.s3_access_key_id,
aws_secret_access_key=settings.s3_secret_access_key,
config=config,
)
self.bucket = settings.s3_bucket
def store(self, file: BinaryIO) -> Tuple[str, int]:
"""
Store a file and return its SHA256 hash and size.
Content-addressable: if the file already exists, just return the hash.
"""
# Read file and compute hash
content = file.read()
sha256_hash = hashlib.sha256(content).hexdigest()
size = len(content)
# Check if already exists
s3_key = f"fruits/{sha256_hash[:2]}/{sha256_hash[2:4]}/{sha256_hash}"
if not self._exists(s3_key):
self.client.put_object(
Bucket=self.bucket,
Key=s3_key,
Body=content,
)
return sha256_hash, size, s3_key
def get(self, s3_key: str) -> bytes:
"""Retrieve a file by its S3 key"""
response = self.client.get_object(Bucket=self.bucket, Key=s3_key)
return response["Body"].read()
def get_stream(self, s3_key: str):
"""Get a streaming response for a file"""
response = self.client.get_object(Bucket=self.bucket, Key=s3_key)
return response["Body"]
def _exists(self, s3_key: str) -> bool:
"""Check if an object exists"""
try:
self.client.head_object(Bucket=self.bucket, Key=s3_key)
return True
except ClientError:
return False
def delete(self, s3_key: str) -> bool:
"""Delete an object"""
try:
self.client.delete_object(Bucket=self.bucket, Key=s3_key)
return True
except ClientError:
return False
# Singleton instance
_storage = None
def get_storage() -> S3Storage:
global _storage
if _storage is None:
_storage = S3Storage()
return _storage