Skip to content

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

ParameterValue
OSDebian 12 (bookworm)
CPU6 cores
RAM11 GB
Swap2 GB (/swapfile, swappiness=10)
IP185.73.215.246

Installed Software

SoftwareVersionNotes
PHP8.3.30 (Sury repo)Extensions: pdo_pgsql, redis, mbstring, xml, curl
Composer2.9.5/usr/local/bin/composer
PostgreSQL16.11Two databases: partizap_prod, partizap_dev
Redis7.0.15Single instance, databases 0–3
PgBouncer1.25.1Transaction mode, scram-sha-256 auth
Nginx1.22.1All server blocks in /etc/nginx/sites-available/default

PHP-FPM Pools

PoolSocketmax_children
partizap-prod/run/php/php8.3-fpm-prod.sock20
partizap-dev/run/php/php8.3-fpm-dev.sock5

Environment Isolation

ResourceProductionDevelopment
Domainpartizap.rudev.partizap.ru
PostgreSQL DBpartizap_prodpartizap_dev
Redis DB0 (prefix: prod:)1 (prefix: dev:)
PHP-FPM poolpartizap-prodpartizap-dev
PgBouncerport 6432direct 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$args routes 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 to location ~ /composer\.(json|lock)$ in both production and development blocks.

Credentials

CredentialLocation
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

BranchEnvironmentDomainPurpose
mainproductionpartizap.ruStable releases
developdevelopmentdev.partizap.ruActive development
feature/*CI onlyFeature 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.php

Frontend 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 main and develop branches 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 cache

Config Layer (8 files)

FilePurpose
config/app.phpLoads .env via phpdotenv, validates required vars, returns full settings array
config/cors.phpCORS allowed origins (partizap.ru, dev.partizap.ru, localhost:3000), methods, headers
config/redis.phpFactory returning RedisConnectionFactory instance
config/doctrine.phpFactory returning EntityManagerInterface (attribute metadata, proxy dir)
config/logging.phpFactory returning Monolog LoggerInterface (file + stderr handlers)
config/container.phpPHP-DI ContainerBuilder with all service definitions
config/middleware.phpRegisters middleware on $app (order: ErrorHandler → CORS → Security → JSON → Routing)
config/routes.phpRegisters 4 route groups with middleware attachment

Infrastructure Layer (3 classes)

RedisConnectionFactory — Creates \Redis instances for 4 logical databases:

ConstantDBPurpose
DB_SESSIONS0PHP sessions (critical)
DB_CACHE1Application cache (flushable)
DB_QUEUES2Symfony Messenger transport
DB_RATE_LIMITS3Rate 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

ClassHTTPError CodeUse Case
AppException(base)(configurable)Abstract base for all app exceptions
NotFoundException404not_foundResource not found
ValidationException422validation_errorInput validation failures (carries field errors)
AuthenticationException401authentication_requiredMissing/invalid session
AuthorizationException403access_deniedInsufficient 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)

MiddlewarePositionBehavior
ErrorHandlerMiddlewareOutermostCatches all exceptions → JSON error responses, logs errors
CorsMiddleware2ndAdds CORS headers, handles OPTIONS preflight with 204
SecurityHeadersMiddleware3rdAdds X-Content-Type-Options, X-Frame-Options, Referrer-Policy
JsonContentTypeMiddleware4thRejects POST/PUT/PATCH without application/json Content-Type (415)
CsrfMiddlewarePass-throughPlaceholder for future CSRF token validation
RateLimitMiddlewarePass-throughPlaceholder for future Redis-based rate limiting
AuthMiddlewareRoute groupChecks $_SESSION['user_id'], throws AuthenticationException
AdminMiddlewareRoute groupChecks $_SESSION['is_admin'], throws AuthorizationException

Domain Layer

User Entity — Doctrine ORM entity mapped to users table:

ColumnTypeNotes
idSERIAL (INT IDENTITY)Auto-generated PK
emailVARCHAR(255)Unique index
password_hashVARCHAR(255)bcrypt hash
display_nameVARCHAR(100)
phoneVARCHAR(20)Nullable
is_activeBOOLEANDefault true
is_adminBOOLEANDefault false
created_atTIMESTAMPImmutable, set on creation
updated_atTIMESTAMPImmutable, updated via @PreUpdate

Route Groups & Actions (12 endpoints)

GroupRouteMethodActionStatus
/healthGETHealthCheckActionWorking (DB + Redis check)
/store/store/productsGETListProductsActionPlaceholder
/store/store/products/{id}GETGetProductActionPlaceholder (404)
/store/store/categoriesGETListCategoriesActionPlaceholder
/auth/auth/loginPOSTLoginActionPlaceholder (501)
/auth/auth/registerPOSTRegisterActionPlaceholder (501)
/auth/auth/logoutPOSTLogoutActionPlaceholder (501)
/auth/auth/meGETMeActionPlaceholder (501)
/vendor/vendor/productsGETListMyProductsActionPlaceholder (requires auth)
/vendor/vendor/productsPOSTCreateProductActionPlaceholder (requires auth)
/admin/admin/dashboardGETDashboardActionPlaceholder (requires auth+admin)
/admin/admin/usersGETListUsersActionPlaceholder (requires auth+admin)

Entry Points

public/index.php — Slim 4 bootstrap sequence:

  1. Load Composer autoloader
  2. Load settings from config/app.php (which loads .env)
  3. Build PHP-DI container from config/container.php
  4. Create Slim app via DI\Bridge\Slim\Bridge::create()
  5. Register middleware from config/middleware.php
  6. Register routes from config/routes.php
  7. $app->run()

bin/console — Symfony Console with Doctrine Migrations commands:

  • Uses DependencyFactory::fromEntityManager() with ExistingEntityManager provider
  • Commands are registered under migrations: prefix (not doctrine:migrations:)
  • Available: diff, migrate, generate, latest, list, status, version, execute, rollup

Tooling

ToolConfigStatus
PHPUnit 11.5phpunit.xml8 tests, 20 assertions — all pass
PHPStan 2.1phpstan.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 users table 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:

RepositoryStackBranch Strategy
team/partizapBackend (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 API

Request format:

  • Content-Type: application/json
  • CSRF: X-CSRF-TOKEN header (value from CSRF_TOKEN cookie)
  • 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:

  1. Backend runs on dev.partizap.ru via Nginx + PHP-FPM (always live on the server)
  2. Frontend runs on localhost:3000 via npm run dev (Nuxt dev server, on developer's machine)
  3. Frontend's $fetch wrapper sends API requests to dev.partizap.ru
  4. CORS allows localhost:3000 origin

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.ru

Testing Strategy

LayerToolStatusWhat to Test
Backend unitPHPUnitImplementedJsonResponder, services, validators, domain logic
Backend featurePHPUnitImplementedAction classes with mocked dependencies
Backend staticPHPStan level 8ImplementedType safety across entire codebase
Frontend unitVitestPlannedComposables, store logic, utility functions
Frontend component@nuxt/test-utilsPlannedVue components with mock API
Frontend E2ETBDPlannedFull 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 models
  • docs/partizap_mvp-design.md — Full API specification with request/response formats
  • docs/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

IssueDetails
Doctrine CLI prefixCommands are migrations:diff not doctrine:migrations:diff (standalone DependencyFactory)
final class mockingRedisConnectionFactory changed from final to regular class for PHPUnit testability
https_proxy on serverUse --noproxy '*' with curl for local testing
Infrastructure doc mismatchdev-prod-infrastructure.md references PHP 8.2, actual install is 8.3
Production Nginxvendor/ blocking rule fixed (was blocking /vendor/* API routes)
Queue workerssystemd services enabled but NOT started (no queue jobs yet)
Placeholder middlewareCsrfMiddleware and RateLimitMiddleware are pass-through — to be implemented