Layered FastAPI backend · Parallel Celery chord processing · Next.js frontend
English · Русский · Contributing · Conduct · Security
A refactoring of the MVP file exchange from the original test task. The application uploads and stores files, checks their traits asynchronously, extracts metadata, and raises alerts.
- the backend is split into HTTP, application, domain, and infrastructure layers;
- the original endpoints, response models, and file-check rules are preserved;
- uploads stream in 1 MiB chunks without reading the whole file into memory;
- blocking file processing is moved off the event loop;
- database, Redis, CORS, and storage configuration is centralized;
- the Docker setup for PostgreSQL, Redis, and the frontend build is fixed;
- deleting a file cascades to its alerts;
- indexes are added for sorting files and alerts by date;
- the frontend is split into API, hooks, features, components, lib, and types;
- backend tests, frontend typecheck, and CI are added.
src/
├── api/ # FastAPI routers and dependency wiring
├── services/ # file use cases and content processing
├── domain/ # domain errors and statuses
├── infrastructure/ # SQLAlchemy repositories and file storage
├── workers/ # Celery app and background tasks
├── core/ # configuration and a single DB session factory
├── models.py # ORM models
├── schemas.py # API schemas
└── app.py # composition root
The API layer only handles HTTP. FileService coordinates use cases and transaction
boundaries. Repositories encapsulate SQLAlchemy queries, and LocalFileStorage
encapsulates disk access. The pure check and metadata-extraction functions are tested
apart from Celery and the database.
In the original implementation the background stages ran sequentially:
scan → metadata → alert
Threat-trait checking uses data from the database, and metadata extraction reads the stored file. These operations do not depend on each other, so the workflow is rebuilt as a Celery chord:
mark processing → (scan ‖ metadata) → finalize + alert
This reduces total processing latency to the slower of the two stages instead of their sum. Redis is used both as the broker and as the result backend required for chord synchronization. The backend and the worker also share one SQLAlchemy session factory instead of duplicate engines and pools.
src/
├── app/ # Next.js page and layout
├── api/ # HTTP client
├── hooks/ # page state and orchestration
├── features/ # the upload user flow
├── components/ # tables and presentation components
├── lib/ # formatting and UI helpers
└── types/ # API types
Components do not know the API address and hold no network logic. Page state lives in
useFileDashboard, and the upload form is isolated as a feature.
Requires Docker with the Compose plugin.
docker compose -f docker-compose.dev.yml up --buildIn another terminal, apply the migrations:
docker exec -it backend alembic upgrade headAfter startup:
- frontend: http://localhost:3000/test
- Swagger UI: http://localhost:8000/docs
PostgreSQL is reachable only by services inside the Docker network on the standard
port 5432.
Backend:
cd backend
uv sync --group dev
uv run ruff check .
uv run pytest -qFrontend:
cd frontend
npm ci
npm run typecheck
npm run buildTests cover CRUD and download through the API, cascade deletion of alerts, streaming storage, the check rules, text and PDF metadata extraction, and the structure of the parallel Celery workflow.
.exe,.bat,.cmd,.sh,.jsare treated as suspicious;- a file larger than 10 MiB needs attention;
- a mismatch between a
.pdfextension and the MIME type needs attention; line_countandchar_countare computed for text;- the page count is estimated for PDFs;
- the processing result raises an
info,warning, orcriticalalert.
The check is based on file traits and is not an antivirus. Local storage suits a single node; in a distributed setup the adapter can be swapped for S3-compatible storage without changing the HTTP or application layers. To guarantee task delivery when the database and the broker fail at the same time, the next step would be a transactional outbox.