From f62b29d39ead1d2b5359bb532d6cd725493fc566 Mon Sep 17 00:00:00 2001 From: nimp-dev Date: Mon, 13 Oct 2025 23:11:04 +0300 Subject: [PATCH 1/2] add pg bases for services --- .env.example | 19 +++- docker-compose.yml | 66 +++++++++++--- makefile | 4 +- postgres-init/01-create-dbs.sql | 4 - services/notification-service/.env.example | 9 +- services/notification-service/composer.json | 3 +- services/order-service/.env.example | 9 +- services/order-service/composer.json | 8 +- services/order-service/config/bundles.php | 2 + .../config/packages/doctrine.yaml | 54 +++++++++++ .../config/packages/doctrine_migrations.yaml | 6 ++ services/order-service/migrations/.gitignore | 0 services/order-service/src/Entity/.gitignore | 0 .../order-service/src/Repository/.gitignore | 0 services/order-service/symfony.lock | 36 ++++++++ services/user-service/.env.example | 11 ++- services/user-service/composer.json | 9 +- services/user-service/config/bundles.php | 3 + .../config/packages/doctrine.yaml | 54 +++++++++++ .../config/packages/doctrine_migrations.yaml | 6 ++ .../config/packages/property_info.yaml | 3 + .../user-service/config/packages/routing.yaml | 2 +- .../config/packages/security.yaml | 39 ++++++++ .../user-service/config/routes/security.yaml | 3 + services/user-service/migrations/.gitignore | 0 .../migrations/Version20251013162129.php | 31 +++++++ .../migrations/Version20251013163439.php | 35 ++++++++ .../src/Controller/UserController.php | 82 ++++++++++++----- services/user-service/src/Entity/.gitignore | 0 services/user-service/src/Entity/User.php | 90 +++++++++++++++++++ .../user-service/src/Repository/.gitignore | 0 .../src/Repository/UserRepository.php | 40 +++++++++ services/user-service/symfony.lock | 61 +++++++++++++ 33 files changed, 627 insertions(+), 62 deletions(-) delete mode 100644 postgres-init/01-create-dbs.sql create mode 100644 services/order-service/config/packages/doctrine.yaml create mode 100644 services/order-service/config/packages/doctrine_migrations.yaml create mode 100644 services/order-service/migrations/.gitignore create mode 100644 services/order-service/src/Entity/.gitignore create mode 100644 services/order-service/src/Repository/.gitignore create mode 100644 services/user-service/config/packages/doctrine.yaml create mode 100644 services/user-service/config/packages/doctrine_migrations.yaml create mode 100644 services/user-service/config/packages/property_info.yaml create mode 100644 services/user-service/config/packages/security.yaml create mode 100644 services/user-service/config/routes/security.yaml create mode 100644 services/user-service/migrations/.gitignore create mode 100644 services/user-service/migrations/Version20251013162129.php create mode 100644 services/user-service/migrations/Version20251013163439.php create mode 100644 services/user-service/src/Entity/.gitignore create mode 100644 services/user-service/src/Entity/User.php create mode 100644 services/user-service/src/Repository/.gitignore create mode 100644 services/user-service/src/Repository/UserRepository.php diff --git a/.env.example b/.env.example index a98c266..ea952db 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,16 @@ -# Database +# Database Configuration POSTGRES_USER=app -POSTGRES_PASSWORD=se3ret -POSTGRES_HOST=postgres -POSTGRES_PORT=5432 \ No newline at end of file +POSTGRES_PASSWORD=strongpassword + +# Database Names +USER_DB_NAME=user_service +ORDER_DB_NAME=order_service +NOTIFICATION_DB_NAME=notification_service + +# Host Ports for Database Access +USER_DB_PORT=5433 +ORDER_DB_PORT=5434 +NOTIFICATION_DB_PORT=5435 + +# Application Secrets +APP_SECRET=your_microservices_secret_here \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 43b6643..f4d1fdd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: container_name: gateway volumes: - ./gateway/nginx.conf:/etc/nginx/conf.d/default.conf:ro - - ./services:/var/www/html # чтобы nginx имел доступ ко всем сервисам + - ./services:/var/www/html ports: - "8080:80" depends_on: @@ -15,50 +15,92 @@ services: user-service: build: ./services/user-service container_name: user-service + env_file: + - .env + - ./services/user-service/.env volumes: - ./services/user-service:/var/www/html environment: - - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/user_service + - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@user-db:5432/${USER_DB_NAME}?serverVersion=15&charset=utf8 depends_on: - - postgres + - user-db expose: - "9000" order-service: build: ./services/order-service container_name: order-service + env_file: + - .env + - ./services/user-service/.env volumes: - ./services/order-service:/var/www/html environment: - - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/order_service + - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@order-db:5432/${ORDER_DB_NAME}?serverVersion=15&charset=utf8 depends_on: - - postgres + - order-db expose: - "9000" notification-service: build: ./services/notification-service container_name: notification-service + env_file: + - .env + - ./services/user-service/.env volumes: - ./services/notification-service:/var/www/html environment: - - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/notification_service + - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@notification-db:5432/${NOTIFICATION_DB_NAME}?serverVersion=15&charset=utf8 depends_on: - - postgres + - notification-db expose: - "9000" - postgres: + # Databases + user-db: image: postgres:15 - container_name: postgres + container_name: user-db + env_file: .env environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${USER_DB_NAME} volumes: - - pgdata:/var/lib/postgresql/data + - user_data:/var/lib/postgresql/data - ./postgres-init:/docker-entrypoint-initdb.d:ro ports: - - "5432:5432" + - "${USER_DB_PORT}:5432" + + order-db: + image: postgres:15 + container_name: order-db + env_file: .env + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${ORDER_DB_NAME} + volumes: + - order_data:/var/lib/postgresql/data + - ./postgres-init:/docker-entrypoint-initdb.d:ro + ports: + - "${ORDER_DB_PORT}:5432" + + notification-db: + image: postgres:15 + container_name: notification-db + env_file: .env + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${NOTIFICATION_DB_NAME} + volumes: + - notification_data:/var/lib/postgresql/data + - ./postgres-init:/docker-entrypoint-initdb.d:ro + ports: + - "${NOTIFICATION_DB_PORT}:5432" volumes: - pgdata: \ No newline at end of file + user_data: + order_data: + notification_data: \ No newline at end of file diff --git a/makefile b/makefile index 4167f16..9c6ff0d 100644 --- a/makefile +++ b/makefile @@ -43,8 +43,8 @@ logs: clean: @echo "🧹 Cleaning up..." docker-compose down -v - rm -f services/*/.env - rm -r services/*/var/ + @rm -f services/*/.env + @rm -rf services/*/var/ 2>/dev/null || true ## Reset everything and start fresh reset: clean setup start diff --git a/postgres-init/01-create-dbs.sql b/postgres-init/01-create-dbs.sql deleted file mode 100644 index 7a2a68f..0000000 --- a/postgres-init/01-create-dbs.sql +++ /dev/null @@ -1,4 +0,0 @@ - -CREATE DATABASE user_service; -CREATE DATABASE order_service; -CREATE DATABASE notification_service; diff --git a/services/notification-service/.env.example b/services/notification-service/.env.example index 288af82..ca78f54 100644 --- a/services/notification-service/.env.example +++ b/services/notification-service/.env.example @@ -1,4 +1,7 @@ APP_ENV=dev -APP_SECRET=change_this_to_a_random_value -DATABASE_URL=postgresql://app:password@postgres:5432/order_service -DEFAULT_URI=http://localhost:8080 + +# Symfony ожидает DATABASE_URL, но в Docker мы её передаём динамически +#Symfony will automatically pick up DATABASE_URL from the container's environment variables. +DATABASE_URL="instal_in_container" +DEFAULT_URI="instal_in_container" + diff --git a/services/notification-service/composer.json b/services/notification-service/composer.json index e4a391f..de3902b 100644 --- a/services/notification-service/composer.json +++ b/services/notification-service/composer.json @@ -14,8 +14,7 @@ "symfony/flex": "^2.8.2", "symfony/framework-bundle": "7.3.*", "symfony/runtime": "7.3.*", - "symfony/yaml": "7.3.*", - "microservices/shared-contracts": "1.*" + "symfony/yaml": "7.3.*" }, "require-dev": { "phpunit/phpunit": "^11.5.42", diff --git a/services/order-service/.env.example b/services/order-service/.env.example index 288af82..ca78f54 100644 --- a/services/order-service/.env.example +++ b/services/order-service/.env.example @@ -1,4 +1,7 @@ APP_ENV=dev -APP_SECRET=change_this_to_a_random_value -DATABASE_URL=postgresql://app:password@postgres:5432/order_service -DEFAULT_URI=http://localhost:8080 + +# Symfony ожидает DATABASE_URL, но в Docker мы её передаём динамически +#Symfony will automatically pick up DATABASE_URL from the container's environment variables. +DATABASE_URL="instal_in_container" +DEFAULT_URI="instal_in_container" + diff --git a/services/order-service/composer.json b/services/order-service/composer.json index b077027..e66da38 100644 --- a/services/order-service/composer.json +++ b/services/order-service/composer.json @@ -9,13 +9,17 @@ "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", + "doctrine/dbal": "^3", + "doctrine/doctrine-bundle": "^2.18", + "doctrine/doctrine-migrations-bundle": "^3.5", + "doctrine/orm": "^3.5", + "microservices/shared-contracts": "1.*", "symfony/console": "7.3.*", "symfony/dotenv": "7.3.*", "symfony/flex": "^2.8.2", "symfony/framework-bundle": "7.3.*", "symfony/runtime": "7.3.*", - "symfony/yaml": "7.3.*", - "microservices/shared-contracts": "1.*" + "symfony/yaml": "7.3.*" }, "require-dev": { "phpunit/phpunit": "^11.5.42", diff --git a/services/order-service/config/bundles.php b/services/order-service/config/bundles.php index 49d3fb6..c1fa06a 100644 --- a/services/order-service/config/bundles.php +++ b/services/order-service/config/bundles.php @@ -2,4 +2,6 @@ return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], ]; diff --git a/services/order-service/config/packages/doctrine.yaml b/services/order-service/config/packages/doctrine.yaml new file mode 100644 index 0000000..25138b9 --- /dev/null +++ b/services/order-service/config/packages/doctrine.yaml @@ -0,0 +1,54 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '16' + + profiling_collect_backtrace: '%kernel.debug%' + use_savepoints: true + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + identity_generation_preferences: + Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + controller_resolver: + auto_mapping: false + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/services/order-service/config/packages/doctrine_migrations.yaml b/services/order-service/config/packages/doctrine_migrations.yaml new file mode 100644 index 0000000..29231d9 --- /dev/null +++ b/services/order-service/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/services/order-service/migrations/.gitignore b/services/order-service/migrations/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/services/order-service/src/Entity/.gitignore b/services/order-service/src/Entity/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/services/order-service/src/Repository/.gitignore b/services/order-service/src/Repository/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/services/order-service/symfony.lock b/services/order-service/symfony.lock index e115c14..9235598 100644 --- a/services/order-service/symfony.lock +++ b/services/order-service/symfony.lock @@ -1,4 +1,40 @@ { + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, + "doctrine/doctrine-bundle": { + "version": "2.18", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb" + }, + "files": [ + "config/packages/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" + ] + }, + "doctrine/doctrine-migrations-bundle": { + "version": "3.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.1", + "ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33" + }, + "files": [ + "config/packages/doctrine_migrations.yaml", + "migrations/.gitignore" + ] + }, "phpstan/phpstan": { "version": "2.1", "recipe": { diff --git a/services/user-service/.env.example b/services/user-service/.env.example index 288af82..b6de8e4 100644 --- a/services/user-service/.env.example +++ b/services/user-service/.env.example @@ -1,4 +1,9 @@ + +# .env APP_ENV=dev -APP_SECRET=change_this_to_a_random_value -DATABASE_URL=postgresql://app:password@postgres:5432/order_service -DEFAULT_URI=http://localhost:8080 + +# Symfony ожидает DATABASE_URL, но в Docker мы её передаём динамически +#Symfony will automatically pick up DATABASE_URL from the container's environment variables. +DATABASE_URL="instal_in_container" +DEFAULT_URI="instal_in_container" + diff --git a/services/user-service/composer.json b/services/user-service/composer.json index b49fd1c..fb3fc32 100644 --- a/services/user-service/composer.json +++ b/services/user-service/composer.json @@ -9,13 +9,18 @@ "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", + "doctrine/dbal": "^3", + "doctrine/doctrine-bundle": "^2.18", + "doctrine/doctrine-migrations-bundle": "^3.5", + "doctrine/orm": "^3.5", + "microservices/shared-contracts": "1.*", "symfony/console": "7.3.*", "symfony/dotenv": "7.3.*", "symfony/flex": "^2.8.2", "symfony/framework-bundle": "7.3.*", "symfony/runtime": "7.3.*", - "symfony/yaml": "7.3.*", - "microservices/shared-contracts": "1.*" + "symfony/security-bundle": "7.3.*", + "symfony/yaml": "7.3.*" }, "require-dev": { "phpunit/phpunit": "^11.5.42", diff --git a/services/user-service/config/bundles.php b/services/user-service/config/bundles.php index 49d3fb6..690585a 100644 --- a/services/user-service/config/bundles.php +++ b/services/user-service/config/bundles.php @@ -2,4 +2,7 @@ return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], ]; diff --git a/services/user-service/config/packages/doctrine.yaml b/services/user-service/config/packages/doctrine.yaml new file mode 100644 index 0000000..25138b9 --- /dev/null +++ b/services/user-service/config/packages/doctrine.yaml @@ -0,0 +1,54 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '16' + + profiling_collect_backtrace: '%kernel.debug%' + use_savepoints: true + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + identity_generation_preferences: + Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + controller_resolver: + auto_mapping: false + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/services/user-service/config/packages/doctrine_migrations.yaml b/services/user-service/config/packages/doctrine_migrations.yaml new file mode 100644 index 0000000..29231d9 --- /dev/null +++ b/services/user-service/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/services/user-service/config/packages/property_info.yaml b/services/user-service/config/packages/property_info.yaml new file mode 100644 index 0000000..dd31b9d --- /dev/null +++ b/services/user-service/config/packages/property_info.yaml @@ -0,0 +1,3 @@ +framework: + property_info: + with_constructor_extractor: true diff --git a/services/user-service/config/packages/routing.yaml b/services/user-service/config/packages/routing.yaml index 0f34f87..281f2c1 100644 --- a/services/user-service/config/packages/routing.yaml +++ b/services/user-service/config/packages/routing.yaml @@ -2,7 +2,7 @@ framework: router: # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands - default_uri: '%env(DEFAULT_URI)%' +# default_uri: '%env(DEFAULT_URI)%' when@prod: framework: diff --git a/services/user-service/config/packages/security.yaml b/services/user-service/config/packages/security.yaml new file mode 100644 index 0000000..367af25 --- /dev/null +++ b/services/user-service/config/packages/security.yaml @@ -0,0 +1,39 @@ +security: + # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords + password_hashers: + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: + users_in_memory: { memory: null } + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + provider: users_in_memory + + # activate different ways to authenticate + # https://symfony.com/doc/current/security.html#the-firewall + + # https://symfony.com/doc/current/security/impersonating_user.html + # switch_user: true + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } + +when@test: + security: + password_hashers: + # By default, password hashers are resource intensive and take time. This is + # important to generate secure password hashes. In tests however, secure hashes + # are not important, waste resources and increase test times. The following + # reduces the work factor to the lowest possible values. + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: + algorithm: auto + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon diff --git a/services/user-service/config/routes/security.yaml b/services/user-service/config/routes/security.yaml new file mode 100644 index 0000000..f853be1 --- /dev/null +++ b/services/user-service/config/routes/security.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/services/user-service/migrations/.gitignore b/services/user-service/migrations/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/services/user-service/migrations/Version20251013162129.php b/services/user-service/migrations/Version20251013162129.php new file mode 100644 index 0000000..83fd9b2 --- /dev/null +++ b/services/user-service/migrations/Version20251013162129.php @@ -0,0 +1,31 @@ +addSql('CREATE SCHEMA public'); + } +} diff --git a/services/user-service/migrations/Version20251013163439.php b/services/user-service/migrations/Version20251013163439.php new file mode 100644 index 0000000..4db0500 --- /dev/null +++ b/services/user-service/migrations/Version20251013163439.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE users (id SERIAL NOT NULL, email VARCHAR(180) NOT NULL, password VARCHAR(255) NOT NULL, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9E7927C74 ON users (email)'); + $this->addSql('COMMENT ON COLUMN users.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN users.updated_at IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP TABLE users'); + } +} diff --git a/services/user-service/src/Controller/UserController.php b/services/user-service/src/Controller/UserController.php index 66aaf76..6c9b072 100644 --- a/services/user-service/src/Controller/UserController.php +++ b/services/user-service/src/Controller/UserController.php @@ -2,63 +2,97 @@ namespace App\Controller; +use App\Entity\User; +use App\Repository\UserRepository; use Microservices\SharedContracts\User\CreateUserRequest; use Microservices\SharedContracts\User\UserResponse; use Microservices\SharedEvents\UserRegisteredEvent; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Attribute\Route; class UserController extends AbstractController { - #[Route('/users', name: 'user_create', methods: ['POST'])] + + public function __construct( + readonly private UserRepository $userRepository, + readonly private UserPasswordHasherInterface $passwordHasher, + ) {} + + #[Route('/create', name: 'user_create', methods: ['POST'])] public function create(Request $request): JsonResponse { // Данные из запроса $data = json_decode($request->getContent(), true); + // Проверяем существует ли пользователь + $existingUser = $this->userRepository->findByEmail($data['email']); + if ($existingUser) { + return $this->json([ + 'error' => 'User with this email already exists' + ], 409); + } + // Создаем DTO из контракта $createRequest = CreateUserRequest::fromArray($data); - // Бизнес-логика создания пользователя - $userId = uniqid('user_'); + // Создаем пользователя + $user = new User(); + $user->setEmail($createRequest->email); + $user->setFirstName($createRequest->firstName); + $user->setLastName($createRequest->lastName); + + $hashedPassword = $this->passwordHasher->hashPassword($user, $createRequest->password); + $user->setPassword($hashedPassword); + + // Сохраняем в БД + $this->userRepository->save($user, true); // Создаем ответ по контракту $userResponse = new UserResponse( - id: $userId, - email: $createRequest->email, - firstName: $createRequest->firstName, - lastName: $createRequest->lastName + id: (string)$user->getId(), // Преобразуем int в string для контракта + email: $user->getEmail(), + firstName: $user->getFirstName(), + lastName: $user->getLastName(), + status: $user->getStatus(), + createdAt: $user->getCreatedAt(), + updatedAt: $user->getUpdatedAt() ); - // Создаем событие (пока просто логируем) + // Создаем событие $event = new UserRegisteredEvent( eventId: uniqid('event_'), - userId: $userId, - email: $createRequest->email, - firstName: $createRequest->firstName, - lastName: $createRequest->lastName, - registeredAt: new \DateTimeImmutable() + userId: (string)$user->getId(), + email: $user->getEmail(), + firstName: $user->getFirstName(), + lastName: $user->getLastName(), + registeredAt: $user->getCreatedAt() ); - // TODO: Отправить событие в message broker (RabbitMQ) - error_log("User registered: " . json_encode($event)); - return $this->json($userResponse, 201); } - #[Route('/users/{id}', name: 'user_get', methods: ['GET'])] - public function get(string $id): JsonResponse + #[Route('/get/{id}', name: 'user_get', methods: ['GET'])] + public function get(int $id): JsonResponse { - // TODO: Получить пользователя из базы данных - // Пока возвращаем mock данные + $user = $this->userRepository->find($id); + + if (!$user) { + return $this->json([ + 'error' => 'User not found' + ], 404); + } $userResponse = new UserResponse( - id: $id, - email: 'user@example.com', - firstName: 'John', - lastName: 'Doe' + id: (string)$user->getId(), + email: $user->getEmail(), + firstName: $user->getFirstName(), + lastName: $user->getLastName(), + status: $user->getStatus(), + createdAt: $user->getCreatedAt(), + updatedAt: $user->getUpdatedAt() ); return $this->json($userResponse); diff --git a/services/user-service/src/Entity/.gitignore b/services/user-service/src/Entity/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/services/user-service/src/Entity/User.php b/services/user-service/src/Entity/User.php new file mode 100644 index 0000000..50472b6 --- /dev/null +++ b/services/user-service/src/Entity/User.php @@ -0,0 +1,90 @@ +createdAt = new \DateTimeImmutable(); + $this->updatedAt = new \DateTimeImmutable(); + } + + // Геттеры + public function getId(): ?string { return $this->id; } + public function getEmail(): ?string { return $this->email; } + public function getFirstName(): ?string { return $this->firstName; } + public function getLastName(): ?string { return $this->lastName; } + public function getPassword(): ?string { return $this->password; } + public function getStatus(): string { return $this->status; } + public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; } + public function getUpdatedAt(): ?\DateTimeImmutable { return $this->updatedAt; } + + // Сеттеры + public function setEmail(string $email): self { + $this->email = $email; + $this->updatedAt = new \DateTimeImmutable(); + return $this; + } + + public function setFirstName(string $firstName): self { + $this->firstName = $firstName; + $this->updatedAt = new \DateTimeImmutable(); + return $this; + } + + public function setLastName(string $lastName): self { + $this->lastName = $lastName; + $this->updatedAt = new \DateTimeImmutable(); + return $this; + } + + public function setPassword(string $password): self { + $this->password = $password; + $this->updatedAt = new \DateTimeImmutable(); + return $this; + } + + public function setStatus(string $status): self { + $this->status = $status; + $this->updatedAt = new \DateTimeImmutable(); + return $this; + } + + public function setUpdatedAt(\DateTimeImmutable $updatedAt): self { + $this->updatedAt = $updatedAt; + return $this; + } +} diff --git a/services/user-service/src/Repository/.gitignore b/services/user-service/src/Repository/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/services/user-service/src/Repository/UserRepository.php b/services/user-service/src/Repository/UserRepository.php new file mode 100644 index 0000000..44307fc --- /dev/null +++ b/services/user-service/src/Repository/UserRepository.php @@ -0,0 +1,40 @@ +getEntityManager()->persist($user); + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + + /** + * @param string $email + * @return User|null + */ + public function findByEmail(string $email): object|null + { + return $this->findOneBy(['email' => $email]); + } + + public function remove(User $user, bool $flush = true): void + { + $this->getEntityManager()->remove($user); + if ($flush) { + $this->getEntityManager()->flush(); + } + } +} diff --git a/services/user-service/symfony.lock b/services/user-service/symfony.lock index 5b59f37..734cefb 100644 --- a/services/user-service/symfony.lock +++ b/services/user-service/symfony.lock @@ -1,4 +1,40 @@ { + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, + "doctrine/doctrine-bundle": { + "version": "2.18", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb" + }, + "files": [ + "config/packages/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" + ] + }, + "doctrine/doctrine-migrations-bundle": { + "version": "3.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.1", + "ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33" + }, + "files": [ + "config/packages/doctrine_migrations.yaml", + "migrations/.gitignore" + ] + }, "phpstan/phpstan": { "version": "1.12", "recipe": { @@ -67,6 +103,18 @@ ".editorconfig" ] }, + "symfony/property-info": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.3", + "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7" + }, + "files": [ + "config/packages/property_info.yaml" + ] + }, "symfony/routing": { "version": "7.3", "recipe": { @@ -79,5 +127,18 @@ "config/packages/routing.yaml", "config/routes.yaml" ] + }, + "symfony/security-bundle": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "2ae08430db28c8eb4476605894296c82a642028f" + }, + "files": [ + "config/packages/security.yaml", + "config/routes/security.yaml" + ] } } From 2a9f1a99bcfbd37e949030f64c61a703a1162cc6 Mon Sep 17 00:00:00 2001 From: nimp-dev Date: Mon, 13 Oct 2025 23:40:43 +0300 Subject: [PATCH 2/2] update after phpStan check --- services/user-service/phpstan.dist.neon | 2 ++ .../user-service/src/Controller/UserController.php | 3 ++- services/user-service/src/Entity/User.php | 10 +++++----- .../user-service/src/Repository/UserRepository.php | 9 +++++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/services/user-service/phpstan.dist.neon b/services/user-service/phpstan.dist.neon index 6e451fa..39255f4 100644 --- a/services/user-service/phpstan.dist.neon +++ b/services/user-service/phpstan.dist.neon @@ -2,3 +2,5 @@ parameters: level: 6 paths: - src + ignoreErrors: + - '#Property App\\Entity\\User::\$id .* is never assigned int#' diff --git a/services/user-service/src/Controller/UserController.php b/services/user-service/src/Controller/UserController.php index 6c9b072..31d25af 100644 --- a/services/user-service/src/Controller/UserController.php +++ b/services/user-service/src/Controller/UserController.php @@ -77,9 +77,10 @@ public function create(Request $request): JsonResponse #[Route('/get/{id}', name: 'user_get', methods: ['GET'])] public function get(int $id): JsonResponse { + /** @var User|null $user */ $user = $this->userRepository->find($id); - if (!$user) { + if (is_null($user)) { return $this->json([ 'error' => 'User not found' ], 404); diff --git a/services/user-service/src/Entity/User.php b/services/user-service/src/Entity/User.php index 50472b6..2ad7745 100644 --- a/services/user-service/src/Entity/User.php +++ b/services/user-service/src/Entity/User.php @@ -31,10 +31,10 @@ class User implements PasswordAuthenticatedUserInterface private string $status = 'active'; #[ORM\Column(type: 'datetime_immutable')] - private ?\DateTimeImmutable $createdAt; + private \DateTimeImmutable $createdAt; #[ORM\Column(type: 'datetime_immutable')] - private ?\DateTimeImmutable $updatedAt; + private \DateTimeImmutable $updatedAt; public function __construct() { @@ -43,14 +43,14 @@ public function __construct() } // Геттеры - public function getId(): ?string { return $this->id; } + public function getId(): ?int { return $this->id; } public function getEmail(): ?string { return $this->email; } public function getFirstName(): ?string { return $this->firstName; } public function getLastName(): ?string { return $this->lastName; } public function getPassword(): ?string { return $this->password; } public function getStatus(): string { return $this->status; } - public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; } - public function getUpdatedAt(): ?\DateTimeImmutable { return $this->updatedAt; } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } + public function getUpdatedAt(): \DateTimeImmutable { return $this->updatedAt; } // Сеттеры public function setEmail(string $email): self { diff --git a/services/user-service/src/Repository/UserRepository.php b/services/user-service/src/Repository/UserRepository.php index 44307fc..8efd23a 100644 --- a/services/user-service/src/Repository/UserRepository.php +++ b/services/user-service/src/Repository/UserRepository.php @@ -5,6 +5,9 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; +/** + * @extends ServiceEntityRepository + */ class UserRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) @@ -25,9 +28,11 @@ public function save(User $user, bool $flush = true): void * @param string $email * @return User|null */ - public function findByEmail(string $email): object|null + public function findByEmail(string $email): User|null { - return $this->findOneBy(['email' => $email]); + /** @var User|null $user */ + $user = $this->findOneBy(['email' => $email]); + return $user; } public function remove(User $user, bool $flush = true): void