Implement Phase 1 backend: FastAPI upload API with MinIO and PostgreSQL
- Add FastAPI app with CORS and auth middleware (Bearer token) - Add POST /api/v1/transactions/upload endpoint (multipart/form-data) - Add MinIO storage service with date-partitioned object keys - Add PostgreSQL database service for transaction metadata - Add Dockerfile and docker-compose.yml for deployment - Add config.example.yaml template Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
0
app/services/__init__.py
Normal file
0
app/services/__init__.py
Normal file
29
app/services/database.py
Normal file
29
app/services/database.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import psycopg
|
||||
from psycopg.rows import dict_row
|
||||
|
||||
from app.config import settings
|
||||
|
||||
|
||||
def _get_conn():
|
||||
return psycopg.connect(settings.postgresql.dsn, row_factory=dict_row)
|
||||
|
||||
|
||||
def insert_transaction(
|
||||
image_object_key: str,
|
||||
original_filename: str | None,
|
||||
user_note: str | None,
|
||||
) -> dict:
|
||||
with _get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO transactions
|
||||
(image_object_key, original_filename, user_note, process_status)
|
||||
VALUES (%s, %s, %s, 'PENDING')
|
||||
RETURNING id, upload_time
|
||||
""",
|
||||
(image_object_key, original_filename, user_note),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
conn.commit()
|
||||
return {"id": str(row["id"]), "upload_time": row["upload_time"].isoformat()}
|
||||
41
app/services/minio.py
Normal file
41
app/services/minio.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
import boto3
|
||||
from botocore.config import Config as BotoConfig
|
||||
|
||||
from app.config import settings
|
||||
|
||||
|
||||
def _get_client():
|
||||
return boto3.client(
|
||||
"s3",
|
||||
endpoint_url=settings.minio.endpoint,
|
||||
aws_access_key_id=settings.minio.access_key,
|
||||
aws_secret_access_key=settings.minio.secret_key,
|
||||
config=BotoConfig(signature_version="s3v4"),
|
||||
)
|
||||
|
||||
|
||||
def _generate_object_key(filename: str) -> str:
|
||||
now = datetime.now(timezone.utc)
|
||||
date_prefix = now.strftime("%Y/%m/%d")
|
||||
ext = PurePosixPath(filename).suffix or ".jpg"
|
||||
return f"{date_prefix}/{uuid.uuid4().hex}{ext}"
|
||||
|
||||
|
||||
def upload_image(file_bytes: bytes, original_filename: str) -> str:
|
||||
client = _get_client()
|
||||
object_key = _generate_object_key(original_filename)
|
||||
content_type = "image/jpeg"
|
||||
if original_filename.lower().endswith(".png"):
|
||||
content_type = "image/png"
|
||||
|
||||
client.put_object(
|
||||
Bucket=settings.minio.bucket,
|
||||
Key=object_key,
|
||||
Body=file_bytes,
|
||||
ContentType=content_type,
|
||||
)
|
||||
return object_key
|
||||
Reference in New Issue
Block a user