Appearance
Partizap Backend — Implementation Report
Date: 2026-02-05 Phase: Backend skeleton initialization Note: This report describes the original single-VPS setup (Debian 12, 185.73.215.246). As of 2026-03-10, the infrastructure has been migrated to two VPS servers (Ubuntu 24.04). See dev-prod-infrastructure.md for current architecture.
1. Server Configuration
Hardware & OS
| Parameter | Value |
|---|---|
| OS | Debian 12 (bookworm) |
| CPU | 6 cores |
| RAM | 11 GB |
| Swap | 2 GB (/swapfile, swappiness=10) |
| IP | 185.73.215.246 |
Installed Software
| Software | Version | Notes |
|---|---|---|
| PHP | 8.3.30 (Sury repo) | Extensions: pdo_pgsql, redis, mbstring, xml, curl |
| Composer | 2.9.5 | /usr/local/bin/composer |
| PostgreSQL | 16.11 | Two databases: partizap_prod, partizap_dev |
| Redis | 7.0.15 | Single instance, databases 0–3 |
| PgBouncer | 1.25.1 | Transaction mode, scram-sha-256 auth |
| Nginx | 1.22.1 | All server blocks in /etc/nginx/sites-available/default |
PHP-FPM Pools
| Pool | Socket | max_children |
|---|---|---|
partizap-prod | /run/php/php8.3-fpm-prod.sock | 20 |
partizap-dev | /run/php/php8.3-fpm-dev.sock | 5 |
Environment Isolation
| Resource | Production | Development |
|---|---|---|
| Domain | partizap.ru | dev.partizap.ru |
| PostgreSQL DB | partizap_prod | partizap_dev |
| Redis DB | 0 (prefix: prod:) | 1 (prefix: dev:) |
| PHP-FPM pool | partizap-prod | partizap-dev |
| PgBouncer | port 6432 | direct PG on 5432 |
| App directory | /var/www/partizap/production/ | /var/www/partizap/development/ |
Nginx Configuration
- HTTPS with Let's Encrypt certificates
try_files $uri /index.php$is_args$argsroutes all requests to Slim- Basic auth on
dev.partizap.ru(credentials in/etc/nginx/.htpasswd) - Security headers: X-Frame-Options, X-Content-Type-Options
- Sensitive file blocking:
.env,.git,composer.json,composer.lock - Fix applied: Original config had
location ~ /(composer\.(json|lock)|vendor/)which blocked/vendor/*API routes. Changed tolocation ~ /composer\.(json|lock)$in both production and development blocks.
Credentials
| Credential | Location |
|---|---|
| PostgreSQL passwords | /var/www/partizap/{production,development}/.env |
| PgBouncer userlist | /etc/pgbouncer/userlist.txt |
| Nginx basic auth | /etc/nginx/.htpasswd |
2. Git Repository Structure
Backend Repository
Location: /var/www/partizap/ (server), git@gitlab.partizap.ru:team/partizap.git (remote)
Branch Strategy
| Branch | Environment | Domain | Purpose |
|---|---|---|---|
main | production | partizap.ru | Stable releases |
develop | development | dev.partizap.ru | Active development |
feature/* | CI only | — | Feature branches, merge into develop |
Repository Contents (develop branch)
/var/www/partizap/
├── .gitignore
├── development/ ← Backend application (Slim 4)
│ ├── .env ← Real credentials (gitignored)
│ ├── .env.example ← Template
│ ├── .gitignore
│ ├── composer.json
│ ├── composer.lock ← (gitignored)
│ ├── vendor/ ← (gitignored)
│ ├── var/ ← (gitignored: cache, proxies)
│ ├── public/index.php
│ ├── bin/console
│ ├── config/
│ ├── app/
│ ├── migrations/
│ ├── tests/
│ ├── phpstan.neon
│ ├── phpunit.xml
│ └── .php-cs-fixer.php
└── production/ ← Production deployment (main branch only)
├── .env
├── .env.example
└── public/index.phpFrontend Repository
Not yet created. Will be a separate Git repository (e.g. team/partizap-frontend) initialized when frontend development begins. The frontend is planned as a Nuxt 4 application that will communicate with the backend API over HTTPS.
GitLab Connection
- Remote:
git@gitlab.partizap.ru:team/partizap.git - Auth: SSH key-based
- Host key added to
~/.ssh/known_hosts - Both
mainanddevelopbranches pushed to origin
3. Backend Architecture
Tech Stack
- Framework: Slim 4.15 + PHP-DI 7.1 (via
php-di/slim-bridge) - ORM: Doctrine ORM 3.6 + DBAL 4.4 + Migrations 3.9
- Database: PostgreSQL 16 (SERIAL PKs, not UUID)
- Cache/Sessions: Redis 7 via native
ext-redis - Logging: Monolog 3.10
- CLI: Symfony Console 7.4
- Testing: PHPUnit 11.5
- Static Analysis: PHPStan 2.1 (level 8) + phpstan-doctrine
- Code Style: PHP-CS-Fixer 3.93
Directory Layout
development/
├── public/index.php ← Entry point (Slim bootstrap)
├── bin/console ← CLI (Doctrine Migrations)
├── config/
│ ├── app.php ← Loads .env, returns settings array
│ ├── container.php ← PHP-DI container builder
│ ├── cors.php ← CORS settings
│ ├── doctrine.php ← EntityManager factory
│ ├── redis.php ← RedisConnectionFactory
│ ├── logging.php ← Monolog factory
│ ├── middleware.php ← Middleware pipeline registration
│ ├── migrations.php ← Doctrine Migrations config
│ └── routes.php ← 4 route groups
├── app/
│ ├── Actions/ ← One class per endpoint
│ │ ├── Store/ ← Public buyer API
│ │ ├── Auth/ ← Authentication
│ │ ├── Vendor/ ← Seller endpoints (auth-protected)
│ │ └── Admin/ ← Admin endpoints (auth+admin)
│ ├── Application/
│ │ ├── Middleware/ ← 8 middleware classes
│ │ ├── Response/JsonResponder.php ← Standard JSON response builder
│ │ └── Exception/ ← Exception hierarchy (5 classes)
│ ├── Domain/
│ │ ├── Entity/User.php ← Doctrine entity with PHP 8 attributes
│ │ └── Repository/ ← Repository interfaces
│ └── Infrastructure/
│ ├── Persistence/ ← Doctrine repository implementations
│ ├── Redis/ ← RedisConnectionFactory
│ └── Logging/ ← RequestIdGenerator
├── migrations/ ← Generated Doctrine migrations
├── tests/
│ ├── bootstrap.php
│ ├── Feature/HealthCheckTest.php
│ └── Unit/JsonResponderTest.php
└── var/
├── doctrine/proxies/ ← Doctrine proxy classes
└── cache/ ← DI compilation cacheConfig Layer (8 files)
| File | Purpose |
|---|---|
config/app.php | Loads .env via phpdotenv, validates required vars, returns full settings array |
config/cors.php | CORS allowed origins (partizap.ru, dev.partizap.ru, localhost:3000), methods, headers |
config/redis.php | Factory returning RedisConnectionFactory instance |
config/doctrine.php | Factory returning EntityManagerInterface (attribute metadata, proxy dir) |
config/logging.php | Factory returning Monolog LoggerInterface (file + stderr handlers) |
config/container.php | PHP-DI ContainerBuilder with all service definitions |
config/middleware.php | Registers middleware on $app (order: ErrorHandler → CORS → Security → JSON → Routing) |
config/routes.php | Registers 4 route groups with middleware attachment |
Infrastructure Layer (3 classes)
RedisConnectionFactory — Creates \Redis instances for 4 logical databases:
| Constant | DB | Purpose |
|---|---|---|
DB_SESSIONS | 0 | PHP sessions (critical) |
DB_CACHE | 1 | Application cache (flushable) |
DB_QUEUES | 2 | Symfony Messenger transport |
DB_RATE_LIMITS | 3 | Rate limiting counters |
Connections are lazy-created and cached per database number. Prefix from .env (dev: or prod:).
DoctrineUserRepository — Implements UserRepositoryInterface with findById(), findByEmail(), save().
RequestIdGenerator — Generates 16-char hex request IDs via random_bytes(8).
Application Layer
Exception Hierarchy
| Class | HTTP | Error Code | Use Case |
|---|---|---|---|
AppException | (base) | (configurable) | Abstract base for all app exceptions |
NotFoundException | 404 | not_found | Resource not found |
ValidationException | 422 | validation_error | Input validation failures (carries field errors) |
AuthenticationException | 401 | authentication_required | Missing/invalid session |
AuthorizationException | 403 | access_denied | Insufficient permissions |
JsonResponder
Standardized JSON response builder with two methods:
respond($data, $status, $meta)→{"data": ..., "meta": {...}}error($code, $message, $status, $details)→{"error": {"code": ..., "message": ..., "details": ...}}
Middleware (8 classes)
| Middleware | Position | Behavior |
|---|---|---|
ErrorHandlerMiddleware | Outermost | Catches all exceptions → JSON error responses, logs errors |
CorsMiddleware | 2nd | Adds CORS headers, handles OPTIONS preflight with 204 |
SecurityHeadersMiddleware | 3rd | Adds X-Content-Type-Options, X-Frame-Options, Referrer-Policy |
JsonContentTypeMiddleware | 4th | Rejects POST/PUT/PATCH without application/json Content-Type (415) |
CsrfMiddleware | Pass-through | Placeholder for future CSRF token validation |
RateLimitMiddleware | Pass-through | Placeholder for future Redis-based rate limiting |
AuthMiddleware | Route group | Checks $_SESSION['user_id'], throws AuthenticationException |
AdminMiddleware | Route group | Checks $_SESSION['is_admin'], throws AuthorizationException |
Domain Layer
User Entity — Doctrine ORM entity mapped to users table:
| Column | Type | Notes |
|---|---|---|
id | SERIAL (INT IDENTITY) | Auto-generated PK |
email | VARCHAR(255) | Unique index |
password_hash | VARCHAR(255) | bcrypt hash |
display_name | VARCHAR(100) | |
phone | VARCHAR(20) | Nullable |
is_active | BOOLEAN | Default true |
is_admin | BOOLEAN | Default false |
created_at | TIMESTAMP | Immutable, set on creation |
updated_at | TIMESTAMP | Immutable, updated via @PreUpdate |
Route Groups & Actions (12 endpoints)
| Group | Route | Method | Action | Status |
|---|---|---|---|---|
| — | /health | GET | HealthCheckAction | Working (DB + Redis check) |
/store | /store/products | GET | ListProductsAction | Placeholder |
/store | /store/products/{id} | GET | GetProductAction | Placeholder (404) |
/store | /store/categories | GET | ListCategoriesAction | Placeholder |
/auth | /auth/login | POST | LoginAction | Placeholder (501) |
/auth | /auth/register | POST | RegisterAction | Placeholder (501) |
/auth | /auth/logout | POST | LogoutAction | Placeholder (501) |
/auth | /auth/me | GET | MeAction | Placeholder (501) |
/vendor | /vendor/products | GET | ListMyProductsAction | Placeholder (requires auth) |
/vendor | /vendor/products | POST | CreateProductAction | Placeholder (requires auth) |
/admin | /admin/dashboard | GET | DashboardAction | Placeholder (requires auth+admin) |
/admin | /admin/users | GET | ListUsersAction | Placeholder (requires auth+admin) |
Entry Points
public/index.php — Slim 4 bootstrap sequence:
- Load Composer autoloader
- Load settings from
config/app.php(which loads.env) - Build PHP-DI container from
config/container.php - Create Slim app via
DI\Bridge\Slim\Bridge::create() - Register middleware from
config/middleware.php - Register routes from
config/routes.php $app->run()
bin/console — Symfony Console with Doctrine Migrations commands:
- Uses
DependencyFactory::fromEntityManager()withExistingEntityManagerprovider - Commands are registered under
migrations:prefix (notdoctrine:migrations:) - Available:
diff,migrate,generate,latest,list,status,version,execute,rollup
Tooling
| Tool | Config | Status |
|---|---|---|
| PHPUnit 11.5 | phpunit.xml | 8 tests, 20 assertions — all pass |
| PHPStan 2.1 | phpstan.neon (level 8) | 0 errors |
| PHP-CS-Fixer 3.93 | .php-cs-fixer.php (PER-CS ruleset) | Configured |
Database Migration
First migration Version20260205195100 generated and applied:
- Creates
userstable with all columns - Creates unique index on
email - Migration tracking table:
doctrine_migration_versions
4. Frontend-Backend Integration Strategy
The frontend (Nuxt 4) is not yet implemented. This section describes how the backend is prepared for frontend integration and the planned architecture.
Repository Structure
Two separate Git repositories are planned:
| Repository | Stack | Branch Strategy |
|---|---|---|
team/partizap | Backend (Slim 4) | main → production, develop → development |
team/partizap-frontend (planned) | Frontend (Nuxt 4) | main → production, develop → development |
The frontend repository will be created when frontend development begins.
Backend API Readiness
The backend exposes a JSON REST API ready for frontend consumption:
Frontend (Nuxt 4) → HTTPS → Nginx → PHP-FPM → Slim 4 APIRequest format:
- Content-Type:
application/json - CSRF:
X-CSRF-TOKENheader (value fromCSRF_TOKENcookie) - SSR: Nitro will proxy browser cookies via
useRequestHeaders(['cookie'])
Response format:
- Success:
{"data": T, "meta": {"has_more": bool, "next_cursor": string|null}} - Error:
{"error": {"code": string, "message": string, "details?": object}}
CORS Configuration
Already configured in config/cors.php to accept frontend requests:
Allowed origins: partizap.ru, dev.partizap.ru, localhost:3000
Allowed methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Credentials: enabled (for session cookies)Planned Development Workflow
Once the frontend is initialized:
- Backend runs on
dev.partizap.ruvia Nginx + PHP-FPM (always live on the server) - Frontend runs on
localhost:3000vianpm run dev(Nuxt dev server, on developer's machine) - Frontend's
$fetchwrapper sends API requests todev.partizap.ru - CORS allows
localhost:3000origin
Backend Deployment
Development (develop branch)
git push origin develop
↓
GitLab CI (optional: run tests, PHPStan)
↓
Deploy to /var/www/partizap/development/
↓
composer install --optimize-autoloader
php bin/console migrations:migrate --no-interaction
↓
Available at dev.partizap.ru (behind basic auth)Production (main branch)
git checkout main
git merge develop
git push origin main
↓
GitLab CI (run full test suite)
↓
Deploy to /var/www/partizap/production/
↓
composer install --no-dev --optimize-autoloader
php bin/console migrations:migrate --no-interaction
↓
Available at partizap.ruTesting Strategy
| Layer | Tool | Status | What to Test |
|---|---|---|---|
| Backend unit | PHPUnit | Implemented | JsonResponder, services, validators, domain logic |
| Backend feature | PHPUnit | Implemented | Action classes with mocked dependencies |
| Backend static | PHPStan level 8 | Implemented | Type safety across entire codebase |
| Frontend unit | Vitest | Planned | Composables, store logic, utility functions |
| Frontend component | @nuxt/test-utils | Planned | Vue components with mock API |
| Frontend E2E | TBD | Planned | Full user flows against dev.partizap.ru |
API Contracts
API contracts are documented in the project docs directory:
docs/autoparts-contracts-v5.md— Entity relationships and data modelsdocs/partizap_mvp-design.md— Full API specification with request/response formatsdocs/partizap_DB-structure.json— Database schema (27 tables)
When the frontend is implemented, Zod schemas and backend validation rules should both derive from these contracts to stay in sync.
5. Known Issues & Notes
| Issue | Details |
|---|---|
| Doctrine CLI prefix | Commands are migrations:diff not doctrine:migrations:diff (standalone DependencyFactory) |
final class mocking | RedisConnectionFactory changed from final to regular class for PHPUnit testability |
https_proxy on server | Use --noproxy '*' with curl for local testing |
| Infrastructure doc mismatch | dev-prod-infrastructure.md references PHP 8.2, actual install is 8.3 |
| Production Nginx | vendor/ blocking rule fixed (was blocking /vendor/* API routes) |
| Queue workers | systemd services enabled but NOT started (no queue jobs yet) |
| Placeholder middleware | CsrfMiddleware and RateLimitMiddleware are pass-through — to be implemented |