Task 001: dockerized monorepo foundation

This commit is contained in:
2026-05-21 16:57:58 +03:00
commit d42f0c4392
83 changed files with 4236 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY apps/backend/requirements.txt ./requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY apps/backend/src ./src
EXPOSE 8000
CMD ["uvicorn", "src.presentation.main:app", "--host", "0.0.0.0", "--port", "8000"]
+5
View File
@@ -0,0 +1,5 @@
fastapi==0.115.6
uvicorn[standard]==0.34.0
psycopg[binary]==3.2.3
redis==5.2.1
boto3==1.35.90
+1
View File
@@ -0,0 +1 @@
"""Backend service package."""
+3
View File
@@ -0,0 +1,3 @@
# Application
Application use cases and workflow orchestration belong in this layer.
+1
View File
@@ -0,0 +1 @@
"""Application layer for use cases and orchestration."""
+3
View File
@@ -0,0 +1,3 @@
# Domain
Business entities, value objects, and domain rules belong in this layer.
+1
View File
@@ -0,0 +1 @@
"""Domain layer for business concepts and invariants."""
@@ -0,0 +1,4 @@
# Infrastructure
Database, cache, object storage, queue, and other external adapters belong in
this layer.
@@ -0,0 +1 @@
"""Infrastructure adapters for external services."""
@@ -0,0 +1,74 @@
from __future__ import annotations
import os
from collections.abc import Callable
import boto3
import psycopg
from botocore.config import Config
from redis import Redis
DependencyStatus = dict[str, str]
def check_dependencies() -> DependencyStatus:
checks: dict[str, Callable[[], None]] = {
"postgres": _check_postgres,
"redis": _check_redis,
"object_storage": _check_object_storage,
}
results: DependencyStatus = {}
for name, check in checks.items():
try:
check()
except Exception:
results[name] = "error"
else:
results[name] = "ok"
return results
def _check_postgres() -> None:
dsn = _required_env("POSTGRES_DSN")
with psycopg.connect(dsn, connect_timeout=3) as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
cursor.fetchone()
def _check_redis() -> None:
redis_url = _required_env("REDIS_URL")
client = Redis.from_url(redis_url, socket_connect_timeout=3, socket_timeout=3)
try:
if client.ping() is not True:
raise RuntimeError("Redis ping failed")
finally:
client.close()
def _check_object_storage() -> None:
endpoint = _required_env("OBJECT_STORAGE_ENDPOINT")
access_key_id = _required_env("OBJECT_STORAGE_ACCESS_KEY_ID")
secret_access_key = _required_env("OBJECT_STORAGE_SECRET_ACCESS_KEY")
bucket = _required_env("OBJECT_STORAGE_BUCKET")
region = os.environ.get("OBJECT_STORAGE_REGION", "us-east-1")
client = boto3.client(
"s3",
endpoint_url=endpoint,
aws_access_key_id=access_key_id,
aws_secret_access_key=secret_access_key,
region_name=region,
config=Config(signature_version="s3v4", connect_timeout=3, read_timeout=3),
)
client.head_bucket(Bucket=bucket)
def _required_env(name: str) -> str:
value = os.environ.get(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value
+3
View File
@@ -0,0 +1,3 @@
# Presentation
HTTP routes, request/response DTOs, and API wiring belong in this layer.
@@ -0,0 +1 @@
"""Presentation layer for HTTP APIs."""
+27
View File
@@ -0,0 +1,27 @@
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from src.infrastructure.dependencies import check_dependencies
app = FastAPI(title="AI Content Pipeline Backend")
@app.get("/health")
def health() -> dict[str, str]:
return {"service": "backend", "status": "ok"}
@app.get("/health/dependencies")
def dependency_health() -> JSONResponse:
dependencies = check_dependencies()
is_ok = all(status == "ok" for status in dependencies.values())
return JSONResponse(
status_code=200 if is_ok else 503,
content={
"service": "backend",
"status": "ok" if is_ok else "error",
"dependencies": dependencies,
},
)
+4
View File
@@ -0,0 +1,4 @@
# Shared
Cross-layer backend primitives belong here. Keep this layer small and avoid
placing domain rules in shared code.
+1
View File
@@ -0,0 +1 @@
"""Shared backend primitives."""