feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
# Local development stack for the finance MVP monorepo.
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.example .env # from infra/
|
||||
# docker compose up -d --wait # full stack (requires backend/ and frontend/ to exist)
|
||||
# docker compose up -d --wait postgres redis minio mailpit # infra-only, before app code lands
|
||||
#
|
||||
# Production runs the same provider-neutral images (infra/docker/) on a hosting
|
||||
# platform that supplies PostgreSQL, Redis, S3-compatible storage, SMTP and a
|
||||
# secret manager located in the Russian Federation. See docs/adr/0004-*.md and
|
||||
# docs/operations/beta-release-runbook.md.
|
||||
|
||||
name: finance
|
||||
|
||||
x-backend-build: &backend-build
|
||||
context: ../backend
|
||||
dockerfile: ../infra/docker/Dockerfile.backend
|
||||
|
||||
x-api-env: &api-env
|
||||
ENVIRONMENT: ${APP_ENV:-development}
|
||||
TZ: ${TZ:-Europe/Moscow}
|
||||
COOKIE_SECURE: "false"
|
||||
# Application-level configuration. Postgres/Redis/S3/SMTP credentials are
|
||||
# constructed from the infra services below; production overrides come from
|
||||
# the platform secret manager only.
|
||||
DATABASE_URL: postgresql+psycopg://${APP_DATABASE_USER:?APP_DATABASE_USER is required}:${APP_DATABASE_PASSWORD:?APP_DATABASE_PASSWORD is required}@postgres:5432/${POSTGRES_DB:-finance}
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
CELERY_BROKER_URL: redis://redis:6379/0
|
||||
S3_ENDPOINT_URL: http://minio:9000
|
||||
S3_USE_SSL: "false"
|
||||
S3_REGION: ${S3_REGION:-us-east-1}
|
||||
S3_ACCESS_KEY_ID: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
|
||||
S3_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
|
||||
S3_BUCKET_STATEMENTS: ${S3_BUCKET_STATEMENTS:-finance-statements}
|
||||
SMTP_HOST: mailpit
|
||||
SMTP_PORT: "1025"
|
||||
SMTP_USE_TLS: "false"
|
||||
SMTP_FROM: ${SMTP_FROM:-no-reply@finance.local}
|
||||
PUBLIC_BASE_URL: http://localhost:${FRONTEND_HOST_PORT:-8080}
|
||||
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET is required}
|
||||
DATA_ENCRYPTION_KEK_ID: ${DATA_ENCRYPTION_KEK_ID:-dev-kek-1}
|
||||
DATA_ENCRYPTION_KEK: ${DATA_ENCRYPTION_KEK:?DATA_ENCRYPTION_KEK is required}
|
||||
IMPORTS_KEK_B64: ${DATA_ENCRYPTION_KEK:?DATA_ENCRYPTION_KEK is required}
|
||||
ADMIN_BOOTSTRAP_TOKEN: ${ADMIN_BOOTSTRAP_TOKEN:?ADMIN_BOOTSTRAP_TOKEN is required}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER is required}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-finance}
|
||||
APP_DATABASE_USER: ${APP_DATABASE_USER:?APP_DATABASE_USER is required}
|
||||
APP_DATABASE_PASSWORD: ${APP_DATABASE_PASSWORD:?APP_DATABASE_PASSWORD is required}
|
||||
TZ: ${TZ:-Europe/Moscow}
|
||||
ports:
|
||||
- "127.0.0.1:${POSTGRES_HOST_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./postgres/init-app-role.sh:/docker-entrypoint-initdb.d/10-app-role.sh:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
networks: [backend]
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
ports:
|
||||
- "127.0.0.1:${REDIS_HOST_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 5s
|
||||
networks: [backend]
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
restart: unless-stopped
|
||||
command: ["server", "/data", "--console-address", ":9001"]
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
|
||||
TZ: ${TZ:-Europe/Moscow}
|
||||
ports:
|
||||
- "127.0.0.1:${MINIO_API_HOST_PORT:-9000}:9000"
|
||||
- "127.0.0.1:${MINIO_CONSOLE_HOST_PORT:-9001}:9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
networks: [backend, edge]
|
||||
|
||||
# Test SMTP sink. Captures all outbound mail; UI at http://127.0.0.1:8025.
|
||||
mailpit:
|
||||
image: axllent/mailpit:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MP_DATA_FILE: /data/mailpit.db
|
||||
TZ: ${TZ:-Europe/Moscow}
|
||||
ports:
|
||||
- "127.0.0.1:${MAILPIT_SMTP_HOST_PORT:-1025}:1025"
|
||||
- "127.0.0.1:${MAILPIT_UI_HOST_PORT:-8025}:8025"
|
||||
volumes:
|
||||
- mailpit_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:8025/"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 5s
|
||||
networks: [backend, edge]
|
||||
|
||||
# FastAPI HTTP process. Never parses statement files: those belong to the worker.
|
||||
migrate:
|
||||
build: *backend-build
|
||||
restart: "no"
|
||||
command: ["alembic", "upgrade", "head"]
|
||||
environment:
|
||||
<<: *api-env
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:?POSTGRES_USER is required}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@postgres:5432/${POSTGRES_DB:-finance}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks: [backend]
|
||||
|
||||
api:
|
||||
build: *backend-build
|
||||
restart: unless-stopped
|
||||
environment: *api-env
|
||||
ports:
|
||||
- "127.0.0.1:${API_HOST_PORT:-8000}:8000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
mailpit:
|
||||
condition: service_started
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request as u; u.urlopen('http://127.0.0.1:8000/healthz', timeout=3)"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 20s
|
||||
networks: [backend, edge]
|
||||
|
||||
# Celery worker for statement parsing, exports and deletion purge.
|
||||
# Production platform must enforce no outbound network access for this service.
|
||||
worker:
|
||||
build: *backend-build
|
||||
restart: unless-stopped
|
||||
command:
|
||||
["celery", "-A", "app.worker.celery", "worker", "--loglevel=INFO", "--concurrency=2", "--prefetch=1", "--acks-late"]
|
||||
environment: *api-env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
networks: [backend]
|
||||
|
||||
beat:
|
||||
build: *backend-build
|
||||
restart: unless-stopped
|
||||
command: ["celery", "-A", "app.worker.celery", "beat", "--loglevel=INFO"]
|
||||
environment: *api-env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
networks: [backend]
|
||||
|
||||
# SPA static files + same-origin reverse proxy for /api/v1 (infra/nginx/nginx.conf).
|
||||
frontend:
|
||||
build:
|
||||
context: ../frontend
|
||||
dockerfile: ../infra/docker/Dockerfile.frontend
|
||||
additional_contexts:
|
||||
infra: ../infra
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:${FRONTEND_HOST_PORT:-8080}:80"
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_started
|
||||
networks: [edge]
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
mailpit_data:
|
||||
|
||||
networks:
|
||||
backend:
|
||||
internal: true
|
||||
edge:
|
||||
Reference in New Issue
Block a user