├── .env ├── .env.local.dist ├── .env.test ├── .env.test.local.dist ├── .github └── workflows │ ├── staging.yml │ └── test.yml ├── .gitignore ├── .gitlab-ci.yml ├── .php-cs-fixer.dist.php ├── Makefile ├── README.md ├── bin ├── console └── phpunit ├── composer.json ├── composer.lock ├── config ├── bundles.php ├── jwt │ └── .gitkeep ├── packages │ ├── cache.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── gesdinet_jwt_refresh_token.yaml │ ├── lexik_jwt_authentication.yaml │ ├── messenger.yaml │ ├── routing.yaml │ └── security.yaml ├── preload.php ├── routes.yaml ├── routes │ └── framework.yaml ├── services.yaml └── services_test.yaml ├── deptrac-layers.yaml ├── deptrac-modules.yaml ├── docker ├── .env.dist ├── .gitignore ├── docker-compose.yml ├── php-fpm │ ├── Dockerfile │ ├── nginx.conf │ ├── run.sh │ ├── supervisord.conf │ └── xdebug.ini └── postgres │ └── Dockerfile ├── docs ├── authorization.md └── index.md ├── phpstan.neon ├── phpunit.xml.dist ├── public └── index.php ├── src ├── Shared │ ├── Application │ │ ├── Command │ │ │ ├── Command.php │ │ │ ├── CommandBusInterface.php │ │ │ ├── CommandHandlerInterface.php │ │ │ └── CommandInterface.php │ │ ├── Event │ │ │ ├── EventBusInterface.php │ │ │ └── EventHandlerInterface.php │ │ ├── Query │ │ │ ├── Query.php │ │ │ ├── QueryBusInterface.php │ │ │ ├── QueryHandlerInterface.php │ │ │ └── QueryInterface.php │ │ └── Security │ │ │ └── AuthChecker.php │ ├── Domain │ │ ├── Aggregate │ │ │ ├── Aggregate.php │ │ │ └── Id.php │ │ ├── Event │ │ │ └── EventInterface.php │ │ ├── Repository │ │ │ ├── Pager.php │ │ │ └── PaginationResult.php │ │ ├── Security │ │ │ ├── AuthUserInterface.php │ │ │ ├── Role.php │ │ │ └── UserFetcherInterface.php │ │ ├── Service │ │ │ ├── AssertService.php │ │ │ └── UlidService.php │ │ ├── Specification │ │ │ └── SpecificationInterface.php │ │ └── ValueObject │ │ │ └── GlobalUserId.php │ └── Infrastructure │ │ ├── Bus │ │ ├── CommandBus.php │ │ ├── EventBus.php │ │ └── QueryBus.php │ │ ├── Controller │ │ ├── .gitignore │ │ └── HealthCheckAction.php │ │ ├── Database │ │ └── Migrations │ │ │ ├── .gitignore │ │ │ └── Version20231203205959.php │ │ ├── EventListener │ │ ├── Doctrine │ │ │ ├── InitSpecificationOnPostLoadListener.php │ │ │ └── PublishDomainEventsOnFlushListener.php │ │ └── Exception │ │ │ └── ExceptionListener.php │ │ ├── Kernel.php │ │ └── Security │ │ └── UserFetcher.php ├── Skills │ ├── Application │ │ ├── DTO │ │ │ ├── Skill │ │ │ │ ├── SkillDTO.php │ │ │ │ └── SkillDTOTransformer.php │ │ │ ├── SkillGroup │ │ │ │ ├── SkillGroupDTO.php │ │ │ │ └── SkillGroupDTOTransformer.php │ │ │ └── Speciality │ │ │ │ ├── SpecialityDTO.php │ │ │ │ ├── SpecialityDTOTransformer.php │ │ │ │ ├── SpecialitySkillDTO.php │ │ │ │ └── SpecialitySkillDTOTransformer.php │ │ ├── Service │ │ │ └── AccessControl │ │ │ │ └── SkillAccessControl.php │ │ └── UseCase │ │ │ ├── AdminUseCaseInteractor.php │ │ │ ├── Command │ │ │ ├── AddSkillToSpeciality │ │ │ │ ├── AddSkillToSpecialityCommand.php │ │ │ │ ├── AddSkillToSpecialityCommandHandler.php │ │ │ │ └── AddSkillToSpecialityCommandResult.php │ │ │ ├── ConfirmSpecialistSkill │ │ │ │ ├── ConfirmSpecialistSkillCommand.php │ │ │ │ └── ConfirmSpecialistSkillCommandHandler.php │ │ │ ├── CreateSkill │ │ │ │ ├── CreateSkillCommand.php │ │ │ │ ├── CreateSkillCommandHandler.php │ │ │ │ └── CreateSkillCommandResult.php │ │ │ ├── CreateSkillGroup │ │ │ │ ├── CreateSkillGroupCommand.php │ │ │ │ ├── CreateSkillGroupCommandHandler.php │ │ │ │ └── CreateSkillGroupCommandResult.php │ │ │ ├── CreateSpeciality │ │ │ │ ├── CreateSpecialityCommand.php │ │ │ │ ├── CreateSpecialityCommandHandler.php │ │ │ │ └── CreateSpecialityCommandResult.php │ │ │ ├── DeleteSkill │ │ │ │ ├── DeleteSkillCommand.php │ │ │ │ ├── DeleteSkillCommandHandler.php │ │ │ │ └── DeleteSkillCommandResult.php │ │ │ ├── DeleteSpeciality │ │ │ │ ├── DeleteSpecialityCommand.php │ │ │ │ ├── DeleteSpecialityCommandHandler.php │ │ │ │ └── DeleteSpecialityCommandResult.php │ │ │ ├── RemoveSkillFromSpeciality │ │ │ │ ├── RemoveSkillFromSpecialityCommand.php │ │ │ │ ├── RemoveSkillFromSpecialityCommandHandler.php │ │ │ │ └── RemoveSkillFromSpecialityCommandResult.php │ │ │ ├── UpdateSpeciality │ │ │ │ ├── UpdateSpecialityCommand.php │ │ │ │ ├── UpdateSpecialityCommandHandler.php │ │ │ │ └── UpdateSpecialityCommandResult.php │ │ │ └── UpdateSpecialityPublicationStatus │ │ │ │ ├── UpdateSpecialityPublicationStatusCommand.php │ │ │ │ ├── UpdateSpecialityPublicationStatusCommandHandler.php │ │ │ │ └── UpdateSpecialityPublicationStatusCommandResult.php │ │ │ ├── PrivateUseCaseInteractor.php │ │ │ ├── PublicUseCaseInteractor.php │ │ │ └── Query │ │ │ ├── FindMySpeciality │ │ │ ├── FindMySpecialityQuery.php │ │ │ ├── FindMySpecialityQueryHandler.php │ │ │ └── FindMySpecialityQueryResult.php │ │ │ ├── FindSkill │ │ │ ├── FindSkillQuery.php │ │ │ ├── FindSkillQueryHandler.php │ │ │ └── FindSkillQueryResult.php │ │ │ ├── FindSkillGroup │ │ │ ├── FindSkillGroupQuery.php │ │ │ ├── FindSkillGroupQueryHandler.php │ │ │ └── FindSkillGroupQueryResult.php │ │ │ ├── FindSpeciality │ │ │ ├── FindSpecialityQuery.php │ │ │ ├── FindSpecialityQueryHandler.php │ │ │ └── FindSpecialityQueryResult.php │ │ │ ├── FindSpecialitySkill │ │ │ ├── FindSpecialitySkillQuery.php │ │ │ ├── FindSpecialitySkillQueryHandler.php │ │ │ └── FindSpecialitySkillQueryResult.php │ │ │ ├── GetMyPagedSpecialities │ │ │ ├── GetMyPagedSpecialitiesQuery.php │ │ │ ├── GetMyPagedSpecialitiesQueryHandler.php │ │ │ └── GetMyPagedSpecialitiesQueryResult.php │ │ │ ├── GetPagedSkillGroups │ │ │ ├── GetPagedSkillGroupsQuery.php │ │ │ ├── GetPagedSkillGroupsQueryHandler.php │ │ │ └── GetPagedSkillGroupsQueryResult.php │ │ │ ├── GetPagedSkills │ │ │ ├── GetPagedSkillsQuery.php │ │ │ ├── GetPagedSkillsQueryHandler.php │ │ │ └── GetPagedSkillsQueryResult.php │ │ │ └── GetPagedSpecialities │ │ │ ├── GetPagedSpecialitiesQuery.php │ │ │ ├── GetPagedSpecialitiesQueryHandler.php │ │ │ └── GetPagedSpecialitiesQueryResult.php │ ├── Domain │ │ ├── Aggregate │ │ │ ├── Skill │ │ │ │ ├── Skill.php │ │ │ │ ├── SkillGroup.php │ │ │ │ └── Specification │ │ │ │ │ ├── SkillGroupNameSpecification.php │ │ │ │ │ ├── SkillSpecification.php │ │ │ │ │ └── UniqueSkillInGroupSpecification.php │ │ │ ├── SkillConfirmation │ │ │ │ ├── Level.php │ │ │ │ ├── Proof.php │ │ │ │ └── SkillConfirmation.php │ │ │ ├── Specialist │ │ │ │ └── Specialist.php │ │ │ └── Speciality │ │ │ │ ├── Level.php │ │ │ │ ├── PublicationStatus.php │ │ │ │ ├── Speciality.php │ │ │ │ ├── SpecialitySkill.php │ │ │ │ └── Specification │ │ │ │ └── SpecialitySpecification.php │ │ ├── Factory │ │ │ ├── SkillConfirmationFactory.php │ │ │ ├── SkillFactory.php │ │ │ ├── SkillGroupFactory.php │ │ │ ├── SpecialistFactory.php │ │ │ └── SpecialityFactory.php │ │ ├── Repository │ │ │ ├── SkillConfirmationRepositoryInterface.php │ │ │ ├── SkillGroupRepositoryInterface.php │ │ │ ├── SkillGroupsFilter.php │ │ │ ├── SkillRepositoryInterface.php │ │ │ ├── SkillsFilter.php │ │ │ ├── SpecialistRepositoryInterface.php │ │ │ ├── SpecialityFilter.php │ │ │ ├── SpecialityRepositoryInterface.php │ │ │ └── SpecialitySkillRepositoryInterface.php │ │ └── Service │ │ │ ├── SkillConfirmationService.php │ │ │ ├── SkillFetcher.php │ │ │ ├── SkillGroupMaker.php │ │ │ ├── SkillMaker.php │ │ │ ├── SpecialistMaker.php │ │ │ ├── SpecialityFetcher.php │ │ │ ├── SpecialityMaker.php │ │ │ └── SpecialitySkillOrganizer.php │ └── Infrastructure │ │ ├── Api │ │ └── SkillsApi.php │ │ ├── Console │ │ └── TestCommand.php │ │ ├── Database │ │ └── ORM │ │ │ └── Aggregate │ │ │ ├── Skill.Skill.orm.xml │ │ │ ├── Skill.SkillGroup.orm.xml │ │ │ ├── SkillConfirmation.Proof.orm.xml │ │ │ ├── SkillConfirmation.SkillConfirmation.orm.xml │ │ │ ├── Specialist.Specialist.orm.xml │ │ │ ├── Speciality.Speciality.orm.xml │ │ │ └── Speciality.SpecialitySkill.orm.xml │ │ └── Repository │ │ ├── SkillConfirmationRepository.php │ │ ├── SkillGroupRepository.php │ │ ├── SkillRepository.php │ │ ├── SpecialistRepository.php │ │ ├── SpecialityRepository.php │ │ └── SpecialitySkillRepository.php ├── Testing │ ├── Application │ │ ├── Command │ │ │ ├── CreateAnswerOption │ │ │ │ ├── CreateAnswerOptionCommand.php │ │ │ │ └── CreateAnswerOptionCommandHandler.php │ │ │ ├── CreateQuestion │ │ │ │ ├── CreateQuestionCommand.php │ │ │ │ └── CreateQuestionCommandHandler.php │ │ │ ├── CreateTest │ │ │ │ ├── CreateTestCommand.php │ │ │ │ └── CreateTestCommandHandler.php │ │ │ └── CreateTestingSession │ │ │ │ └── CreateTestingSessionCommand.php │ │ ├── CommandInteractor.php │ │ ├── Query │ │ │ ├── DTO │ │ │ │ ├── Test │ │ │ │ │ ├── AnswerOptionDTO.php │ │ │ │ │ ├── QuestionDTO.php │ │ │ │ │ └── TestDTO.php │ │ │ │ └── TestingSessionDTO.php │ │ │ ├── FindAnswerOption │ │ │ │ ├── FindAnswerOptionQuery.php │ │ │ │ ├── FindAnswerOptionQueryHandler.php │ │ │ │ └── FindAnswerOptionQueryResult.php │ │ │ ├── FindQuestion │ │ │ │ ├── FindQuestionQuery.php │ │ │ │ ├── FindQuestionQueryHandler.php │ │ │ │ └── FindQuestionQueryResult.php │ │ │ ├── FindTest │ │ │ │ ├── FindTestQuery.php │ │ │ │ ├── FindTestQueryHandler.php │ │ │ │ └── FindTestQueryResult.php │ │ │ └── FindTestingSession │ │ │ │ ├── FindTestingSessionQuery.php │ │ │ │ ├── FindTestingSessionQueryHandler.php │ │ │ │ └── FindTestingSessionQueryResult.php │ │ └── QueryInteractor.php │ ├── Domain │ │ ├── Aggregate │ │ │ ├── Test │ │ │ │ ├── AnswerOption.php │ │ │ │ ├── DifficultyLevel.php │ │ │ │ ├── Question.php │ │ │ │ ├── QuestionType.php │ │ │ │ └── Test.php │ │ │ └── TestingSession │ │ │ │ ├── TestingSession.php │ │ │ │ └── UserAnswer.php │ │ ├── Event │ │ │ └── TestingSessionCompletedEvent.php │ │ ├── Factory │ │ │ ├── AnswerOptionFactory.php │ │ │ ├── QuestionFactory.php │ │ │ ├── TestFactory.php │ │ │ └── TestingSessionFactory.php │ │ ├── Repository │ │ │ ├── AnswerOptionRepositoryInterface.php │ │ │ ├── QuestionRepositoryInterface.php │ │ │ ├── TestRepositoryInterface.php │ │ │ └── TestingSessionRepositoryInterface.php │ │ ├── Service │ │ │ └── TestingSessionMaker.php │ │ └── Specification │ │ │ ├── TestSpecification.php │ │ │ └── UniqueTestNameSpecification.php │ └── Infrastructure │ │ ├── Adapter │ │ ├── SkillsAdapter.php │ │ └── SkillsApiInterface.php │ │ ├── Database │ │ └── ORM │ │ │ └── Aggregate │ │ │ ├── Test.AnswerOption.orm.xml │ │ │ ├── Test.Question.orm.xml │ │ │ ├── Test.Test.orm.xml │ │ │ ├── TestingSession.TestingSession.orm.xml │ │ │ └── TestingSession.UserAnswer.orm.xml │ │ ├── EventHandler │ │ └── TestingSessionCompletedEventHandler.php │ │ └── Repository │ │ ├── AnswerOptionRepository.php │ │ ├── QuestionRepository.php │ │ ├── TestRepository.php │ │ └── TestingSessionRepository.php └── Users │ ├── Application │ ├── DTO │ │ └── UserDTO.php │ └── UseCase │ │ ├── AdminUseCaseInteractor.php │ │ ├── Command │ │ └── CreateUser │ │ │ ├── CreateUserCommand.php │ │ │ └── CreateUserCommandHandler.php │ │ ├── PrivateUseCaseInteractor.php │ │ ├── PublicUseCaseInteractor.php │ │ └── Query │ │ ├── FindUser │ │ ├── FindUserQuery.php │ │ ├── FindUserQueryHandler.php │ │ └── FindUserQueryResult.php │ │ ├── FindUserByEmail │ │ ├── FindUserByEmailQuery.php │ │ └── FindUserByEmailQueryHandler.php │ │ └── GetMe │ │ ├── GetMeQuery.php │ │ ├── GetMeQueryHandler.php │ │ └── GetMeQueryResult.php │ ├── Domain │ ├── Entity │ │ ├── RefreshToken.php │ │ └── User.php │ ├── Factory │ │ └── UserFactory.php │ ├── Repository │ │ └── UserRepositoryInterface.php │ └── Service │ │ └── UserPasswordHasherInterface.php │ └── Infrastructure │ ├── Console │ └── CreateUserConsoleCommand.php │ ├── Controller │ └── GetMeAction.php │ ├── Database │ └── ORM │ │ ├── RefreshToken.orm.xml │ │ └── User.orm.xml │ ├── Repository │ └── UserRepository.php │ └── UserPasswordHasher.php ├── symfony.lock └── tests ├── Functional ├── Api │ ├── ApiTestCase.php │ └── AuthApiTestCase.php ├── Shared │ └── Infrastructure │ │ └── Controller │ │ └── HealthCheckActionTest.php ├── Skills │ └── Infrastructure │ │ └── Repository │ │ └── SkillGroupRepositoryTest.php ├── Testing │ └── Domain │ │ └── Entity │ │ └── TestingSession │ │ └── TestingSessionCompletionTest.php └── Users │ ├── Application │ ├── Command │ │ └── CreateUser │ │ │ └── CreateUserCommandHandlerTest.php │ └── Query │ │ └── FindUserByEmail │ │ └── FindUserByEmailQueryHandlerTest.php │ └── Infrastructure │ └── Repository │ └── UserRepositoryTest.php ├── Resource └── Fixture │ ├── Skills │ ├── SkillFixture.php │ └── SkillGroupFixture.php │ └── Users │ └── UserFixture.php ├── Tools ├── DITools.php ├── FakerTools.php └── FixtureTools.php └── bootstrap.php /.env: -------------------------------------------------------------------------------- 1 | #APP_ENV=prod 2 | #APP_SECRET=4dc864551c62905089314503222dccc748 3 | 4 | DB_HOST= 5 | DB_NAME= 6 | DB_USER= 7 | DB_PASSWORD= 8 | DB_PORT= 9 | 10 | #JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem 11 | #JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem 12 | JWT_SECRET_KEY=# Required base64 encoded Private Key 13 | JWT_PUBLIC_KEY=# Required base64 encoded Public Key 14 | JWT_TOKEN_TTL=3600 15 | JWT_PASSPHRASE=# Required 16 | -------------------------------------------------------------------------------- /.env.test.local.dist: -------------------------------------------------------------------------------- 1 | DB_HOST=postgres 2 | DB_NAME=symfony 3 | DB_USER=symfony 4 | DB_PASSWORD=symfony 5 | DB_PORT=5432 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | 3 | ###> symfony/framework-bundle ### 4 | /.env.local 5 | /.env.local.php 6 | /.env.*.local 7 | /config/secrets/prod/prod.decrypt.private.php 8 | /public/bundles/ 9 | /var/ 10 | /vendor/ 11 | ###< symfony/framework-bundle ### 12 | 13 | ###> symfony/phpunit-bridge ### 14 | .phpunit.result.cache 15 | /phpunit.xml 16 | ###< symfony/phpunit-bridge ### 17 | ###> lexik/jwt-authentication-bundle ### 18 | /config/jwt/*.pem 19 | ###< lexik/jwt-authentication-bundle ### 20 | 21 | ###> friendsofphp/php-cs-fixer ### 22 | /.php-cs-fixer.php 23 | /.php-cs-fixer.cache 24 | ###< friendsofphp/php-cs-fixer ### 25 | 26 | .deptrac.cache 27 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude(['var', 'vendor']); 6 | 7 | return (new PhpCsFixer\Config()) 8 | ->setRules([ 9 | '@Symfony' => true, 10 | 'php_unit_method_casing' => ['case' => 'snake_case'], 11 | ]) 12 | ->setFinder($finder); 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ['all' => true], 5 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 6 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 7 | Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], 8 | Liip\TestFixturesBundle\LiipTestFixturesBundle::class => ['dev' => true, 'test' => true], 9 | DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['dev' => true, 'test' => true], 10 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 11 | Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], 12 | Gesdinet\JWTRefreshTokenBundle\GesdinetJWTRefreshTokenBundle::class => ['all' => true], 13 | ]; 14 | -------------------------------------------------------------------------------- /config/jwt/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandro-yakovlev/symfony-docker/962cd7c94de822b37464eafbc493f0ea96423c02/config/jwt/.gitkeep -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as Migrations classes should NOT be autoloaded 5 | 'App\Shared\Infrastructure\Database\Migrations': '%kernel.project_dir%/src/Shared/Infrastructure/Database/Migrations' 6 | enable_profiler: '%kernel.debug%' 7 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | #csrf_protection: true 5 | http_method_override: false 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | # Remove or comment this section to explicitly disable session support. 9 | session: 10 | handler_id: null 11 | cookie_secure: auto 12 | cookie_samesite: lax 13 | storage_factory_id: session.storage.factory.native 14 | 15 | #esi: true 16 | #fragments: true 17 | php_errors: 18 | log: true 19 | 20 | when@test: 21 | framework: 22 | test: true 23 | session: 24 | storage_factory_id: session.storage.factory.mock_file 25 | -------------------------------------------------------------------------------- /config/packages/gesdinet_jwt_refresh_token.yaml: -------------------------------------------------------------------------------- 1 | gesdinet_jwt_refresh_token: 2 | ttl: 2592000 # 30 days 3 | token_parameter_name: refreshToken 4 | refresh_token_class: App\Users\Domain\Entity\RefreshToken -------------------------------------------------------------------------------- /config/packages/lexik_jwt_authentication.yaml: -------------------------------------------------------------------------------- 1 | lexik_jwt_authentication: 2 | # secret_key: '%env(resolve:JWT_SECRET_KEY)%' 3 | # public_key: '%env(resolve:JWT_PUBLIC_KEY)%' 4 | secret_key: '%env(base64:JWT_SECRET_KEY)%' 5 | public_key: '%env(base64:JWT_PUBLIC_KEY)%' 6 | pass_phrase: '%env(JWT_PASSPHRASE)%' 7 | token_ttl: '%env(resolve:JWT_TOKEN_TTL)%' 8 | user_identity_field: email -------------------------------------------------------------------------------- /config/packages/messenger.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | messenger: 3 | default_bus: command.bus 4 | buses: 5 | command.bus: 6 | middleware: 7 | - doctrine_transaction 8 | query.bus: 9 | event.bus: 10 | default_middleware: allow_no_handlers 11 | 12 | # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. 13 | # failure_transport: failed 14 | 15 | transports: 16 | # https://symfony.com/doc/current/messenger.html#transport-configuration 17 | # async: '%env(MESSENGER_TRANSPORT_DSN)%' 18 | # failed: 'doctrine://default?queue_name=failed' 19 | # sync: 'sync://' 20 | 21 | routing: 22 | # Route your messages to the transports 23 | # 'App\Message\YourMessage': async 24 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /config/preload.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | tests 23 | 24 | 25 | 26 | 27 | 28 | src 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | authorizationChecker->isGranted($attribute, $subject); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Shared/Domain/Aggregate/Aggregate.php: -------------------------------------------------------------------------------- 1 | events; 24 | $this->events = []; 25 | 26 | return $events; 27 | } 28 | 29 | public function eventsEmpty(): bool 30 | { 31 | return empty($this->events); 32 | } 33 | 34 | protected function raise(EventInterface $event): void 35 | { 36 | $this->events[] = $event; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Shared/Domain/Aggregate/Id.php: -------------------------------------------------------------------------------- 1 | page) { 24 | return 0; 25 | } 26 | 27 | return $this->page * $this->perPage - $this->perPage; 28 | } 29 | 30 | public function getLimit(): int 31 | { 32 | return $this->perPage; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Shared/Domain/Repository/PaginationResult.php: -------------------------------------------------------------------------------- 1 | id = $id; 16 | } 17 | 18 | public function getId(): string 19 | { 20 | return $this->id; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Bus/CommandBus.php: -------------------------------------------------------------------------------- 1 | messageBus = $commandBus; 20 | } 21 | 22 | public function execute(CommandInterface $command): mixed 23 | { 24 | try { 25 | return $this->handle($command); 26 | } catch (HandlerFailedException $exception) { 27 | throw $exception->getNestedExceptions()[0]; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Bus/EventBus.php: -------------------------------------------------------------------------------- 1 | messageBus = $eventBus; 19 | } 20 | 21 | public function execute(EventInterface ...$events): void 22 | { 23 | foreach ($events as $event) { 24 | $this->messageBus->dispatch($event); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Bus/QueryBus.php: -------------------------------------------------------------------------------- 1 | messageBus = $queryBus; 20 | } 21 | 22 | public function execute(QueryInterface $query): mixed 23 | { 24 | try { 25 | return $this->handle($query); 26 | } catch (HandlerFailedException $exception) { 27 | throw $exception->getNestedExceptions()[0]; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandro-yakovlev/symfony-docker/962cd7c94de822b37464eafbc493f0ea96423c02/src/Shared/Infrastructure/Controller/.gitignore -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Controller/HealthCheckAction.php: -------------------------------------------------------------------------------- 1 | 'ok']); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Database/Migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandro-yakovlev/symfony-docker/962cd7c94de822b37464eafbc493f0ea96423c02/src/Shared/Infrastructure/Database/Migrations/.gitignore -------------------------------------------------------------------------------- /src/Shared/Infrastructure/EventListener/Doctrine/InitSpecificationOnPostLoadListener.php: -------------------------------------------------------------------------------- 1 | getObject(); 23 | 24 | $reflect = new \ReflectionClass($entity); 25 | 26 | foreach ($reflect->getProperties() as $property) { 27 | $type = $property->getType(); 28 | 29 | if (is_null($type) || $property->isInitialized($entity)) { 30 | continue; 31 | } 32 | 33 | if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) { 34 | // initialize specifications 35 | $interfaces = class_implements($type->getName()); 36 | if (isset($interfaces[SpecificationInterface::class])) { 37 | $property->setValue($entity, $this->container->get($type->getName())); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Shared/Infrastructure/Kernel.php: -------------------------------------------------------------------------------- 1 | security->getUser(); 23 | 24 | if (is_null($user)) { 25 | throw new AccessDeniedException('Access Denied.'); 26 | } 27 | 28 | Assert::isInstanceOf($user, AuthUserInterface::class, sprintf('Invalid user type %s', \get_class($user))); 29 | 30 | return $user; 31 | } 32 | 33 | public function nullableUser(): ?AuthUserInterface 34 | { 35 | /** @var AuthUserInterface|null $user */ 36 | $user = $this->security->getUser(); 37 | 38 | return $user; 39 | } 40 | 41 | public function requiredUserId(): string 42 | { 43 | return $this->requiredUser()->getId(); 44 | } 45 | 46 | public function nullableUserId(): ?string 47 | { 48 | return $this->nullableUser()?->getId(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Skills/Application/DTO/Skill/SkillDTO.php: -------------------------------------------------------------------------------- 1 | fromSkillEntity($entity); 23 | } 24 | 25 | return $skills; 26 | } 27 | 28 | public function fromSkillEntity(Skill $skill): SkillDTO 29 | { 30 | $dto = new SkillDTO(); 31 | $dto->id = $skill->getId(); 32 | $dto->name = $skill->getName(); 33 | 34 | $skillGroup = new SkillGroupDTO(); 35 | $skillGroup->id = $skill->getSkillGroup()->getId(); 36 | $skillGroup->name = $skill->getSkillGroup()->getName(); 37 | $dto->skillGroup = $skillGroup; 38 | 39 | return $dto; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Skills/Application/DTO/SkillGroup/SkillGroupDTO.php: -------------------------------------------------------------------------------- 1 | id = $entity->getId(); 15 | $dto->name = $entity->getName(); 16 | 17 | return $dto; 18 | } 19 | 20 | /** 21 | * @param array $skillGroups 22 | * 23 | * @return array 24 | */ 25 | public function fromEntityList(array $skillGroups): array 26 | { 27 | $skillGroupDTOs = []; 28 | foreach ($skillGroups as $skillGroup) { 29 | $skillGroupDTOs[] = $this->fromEntity($skillGroup); 30 | } 31 | 32 | return $skillGroupDTOs; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Skills/Application/DTO/Speciality/SpecialityDTO.php: -------------------------------------------------------------------------------- 1 | getSkills()->toArray() as $skill) { 19 | $skills[] = $this->specialitySkillDTOTransformer->fromEntity($skill); 20 | } 21 | 22 | return new SpecialityDTO( 23 | id: $speciality->getId(), 24 | name: $speciality->getName(), 25 | description: $speciality->getDescription(), 26 | publicationStatus: $speciality->getPublicationStatus()->value, 27 | skills: $skills 28 | ); 29 | } 30 | 31 | /** 32 | * @param Speciality[] $specialities 33 | * 34 | * @return SpecialityDTO[] 35 | */ 36 | public function fromEntities(array $specialities): array 37 | { 38 | $specialitiesDTO = []; 39 | foreach ($specialities as $speciality) { 40 | $specialitiesDTO[] = $this->fromEntity($speciality); 41 | } 42 | 43 | return $specialitiesDTO; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Skills/Application/DTO/Speciality/SpecialitySkillDTO.php: -------------------------------------------------------------------------------- 1 | skillDTOTransformer->fromSkillEntity($specialitySkill->getSkill()); 20 | 21 | return new SpecialitySkillDTO( 22 | id: $specialitySkill->getId(), 23 | specialityId: $specialitySkill->getId(), 24 | skill: $skillDTO, 25 | level: $specialitySkill->getLevel()->value, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Skills/Application/Service/AccessControl/SkillAccessControl.php: -------------------------------------------------------------------------------- 1 | isAdmin()) { 30 | return true; 31 | } 32 | 33 | $skill = $this->skillFetcher->getRequiredSkill($skillId); 34 | 35 | return $skill->isOwnedBy($userId); 36 | } 37 | 38 | /** 39 | * Может ли пользователь добавить навык в специальность? 40 | */ 41 | public function canAddSkillToSpeciality(string $userId, string $skillId, string $specialityId): bool 42 | { 43 | if ($this->isAdmin()) { 44 | return true; 45 | } 46 | 47 | $skill = $this->skillFetcher->getRequiredSkill($skillId); 48 | $speciality = $this->specialityFetcher->getRequiredSpeciality($specialityId); 49 | 50 | // Пользователь может добавить навык в специальность, если он является владельцем навыка и специальности 51 | return $skill->isOwnedBy($userId) && $speciality->isOwnedBy($userId); 52 | } 53 | 54 | private function isAdmin(): bool 55 | { 56 | return $this->authChecker->isGranted(Role::ROLE_ADMIN); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/AdminUseCaseInteractor.php: -------------------------------------------------------------------------------- 1 | commandBus->execute($command); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/AddSkillToSpeciality/AddSkillToSpecialityCommand.php: -------------------------------------------------------------------------------- 1 | skillAccessControl 26 | ->canAddSkillToSpeciality( 27 | $this->userFetcher->requiredUserId(), 28 | $command->skillId, 29 | $command->specialityId 30 | ), 31 | 'Запрещено' 32 | ); 33 | 34 | $specialitySkill = $this->specialitySkillOrganizer->addSkillToSpeciality( 35 | $command->skillId, 36 | $command->specialityId, 37 | $command->level 38 | ); 39 | 40 | return new AddSkillToSpecialityCommandResult($specialitySkill->getId()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/AddSkillToSpeciality/AddSkillToSpecialityCommandResult.php: -------------------------------------------------------------------------------- 1 | skillConfirmationService->confirm($command->userId, $command->skillId, $command->testId, $command->correctAnswersPercentage); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/CreateSkill/CreateSkillCommand.php: -------------------------------------------------------------------------------- 1 | skillMaker->make( 22 | $command->name, 23 | $command->skillGroupId, 24 | $this->userFetcher->requiredUser()->getId() 25 | ); 26 | 27 | return new CreateSkillCommandResult($skill->getId()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/CreateSkill/CreateSkillCommandResult.php: -------------------------------------------------------------------------------- 1 | skillGroupMaker->make( 22 | $command->name, 23 | $this->userFetcher->requiredUser()->getId() 24 | ); 25 | 26 | return new CreateSkillGroupCommandResult($skillGroup->getId()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/CreateSkillGroup/CreateSkillGroupCommandResult.php: -------------------------------------------------------------------------------- 1 | userFetcher->requiredUser()->getId(); 22 | $speciality = $this->specialityMaker->make($command->name, $ownerId); 23 | 24 | return new CreateSpecialityCommandResult($speciality->getId()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/CreateSpeciality/CreateSpecialityCommandResult.php: -------------------------------------------------------------------------------- 1 | skillAccessControl 28 | ->canDeleteSkill($this->userFetcher->requiredUserId(), $command->skillId), 29 | 'Запрещено' 30 | ); 31 | 32 | $skill = $this->skillFetcher->getRequiredSkill($command->skillId); 33 | $this->skillRepository->delete($skill); 34 | 35 | return new DeleteSkillCommandResult(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/DeleteSkill/DeleteSkillCommandResult.php: -------------------------------------------------------------------------------- 1 | specialityFetcher->getRequiredSpeciality($command->specialityId); 22 | $speciality->delete(); 23 | $this->specialityRepository->add($speciality); 24 | 25 | return new DeleteSpecialityCommandResult(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/DeleteSpeciality/DeleteSpecialityCommandResult.php: -------------------------------------------------------------------------------- 1 | specialitySkillRepository->findOne($command->specialitySkillId); 19 | $this->specialitySkillRepository->remove($specialitySkill); 20 | 21 | return new RemoveSkillFromSpecialityCommandResult(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Command/RemoveSkillFromSpeciality/RemoveSkillFromSpecialityCommandResult.php: -------------------------------------------------------------------------------- 1 | queryBus->execute($query); 29 | } 30 | 31 | public function getPagedSkillGroups(SkillGroupsFilter $filter): GetPagedSkillGroupsQueryResult 32 | { 33 | $query = new GetPagedSkillGroupsQuery($filter); 34 | 35 | return $this->queryBus->execute($query); 36 | } 37 | 38 | public function getPagedSpecialities(SpecialityFilter $filter): GetPagedSpecialitiesQueryResult 39 | { 40 | $query = new GetPagedSpecialitiesQuery($filter); 41 | 42 | return $this->queryBus->execute($query); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/FindMySpeciality/FindMySpecialityQuery.php: -------------------------------------------------------------------------------- 1 | specialityRepository->findOne( 24 | new SpecialityFilter( 25 | publicationStatuses: [ 26 | PublicationStatus::PUBLISHED->value, 27 | PublicationStatus::DRAFT->value, 28 | ], 29 | id: $query->id 30 | ) 31 | ); 32 | 33 | if (!$speciality) { 34 | return new FindMySpecialityQueryResult(null); 35 | } 36 | 37 | $specialityDTO = $this->specialityDTOTransformer->fromEntity($speciality); 38 | 39 | return new FindMySpecialityQueryResult($specialityDTO); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/FindMySpeciality/FindMySpecialityQueryResult.php: -------------------------------------------------------------------------------- 1 | skillRepository->findOneById($query->id); 22 | 23 | if (!$skill) { 24 | return new FindSkillQueryResult(null); 25 | } 26 | 27 | $dto = $this->skillDTOHydrator->fromSkillEntity($skill); 28 | 29 | return new FindSkillQueryResult($dto); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/FindSkill/FindSkillQueryResult.php: -------------------------------------------------------------------------------- 1 | skillGroupRepository->findOneById($query->id); 22 | 23 | if (!$skillGroup) { 24 | return new FindSkillGroupQueryResult(null); 25 | } 26 | 27 | return new FindSkillGroupQueryResult( 28 | $this->skillGroupDTOHydrator->fromEntity($skillGroup) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/FindSkillGroup/FindSkillGroupQueryResult.php: -------------------------------------------------------------------------------- 1 | specialityRepository->findOne( 24 | new SpecialityFilter( 25 | publicationStatuses: [PublicationStatus::PUBLISHED->value], 26 | id: $query->id 27 | ) 28 | ); 29 | 30 | if (!$speciality) { 31 | return new FindSpecialityQueryResult(null); 32 | } 33 | 34 | $specialityDTO = $this->specialityDTOTransformer->fromEntity($speciality); 35 | 36 | return new FindSpecialityQueryResult($specialityDTO); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/FindSpeciality/FindSpecialityQueryResult.php: -------------------------------------------------------------------------------- 1 | specialitySkillRepository->findOne($query->id); 22 | $specialitySkillDTO = $this->specialitySkillDTOTransformer->fromEntity($specialitySkill); 23 | 24 | return new FindSpecialitySkillQueryResult($specialitySkillDTO); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/FindSpecialitySkill/FindSpecialitySkillQueryResult.php: -------------------------------------------------------------------------------- 1 | specialitySkill); 18 | 19 | return $this; 20 | } 21 | 22 | public function onError(callable $callback): self 23 | { 24 | $callback($this->specialitySkill); 25 | 26 | return $this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/GetMyPagedSpecialities/GetMyPagedSpecialitiesQuery.php: -------------------------------------------------------------------------------- 1 | filter->ownerId = $this->userFetcher->requiredUser()->getId(); 27 | 28 | // Доступны только опубликованные специальности и черновики 29 | $query->filter->publicationStatuses = [ 30 | PublicationStatus::PUBLISHED->value, 31 | PublicationStatus::DRAFT->value, 32 | ]; 33 | 34 | $pagination = $this->specialityRepository->findPagedItems($query->filter); 35 | $specialities = $this->specialityDTOHydrator->fromEntities($pagination->items); 36 | 37 | return new GetMyPagedSpecialitiesQueryResult( 38 | $specialities, 39 | new Pager( 40 | $query->filter->pager->page, 41 | $query->filter->pager->perPage, 42 | $pagination->total 43 | ) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/GetMyPagedSpecialities/GetMyPagedSpecialitiesQueryResult.php: -------------------------------------------------------------------------------- 1 | skillGroupRepository->findByFilter($query->filter); 23 | $skillGroups = $this->skillGroupDTOHydrator->fromEntityList($paginator->items); 24 | $pager = new Pager( 25 | $query->filter->pager->page, 26 | $query->filter->pager->perPage, 27 | $paginator->total 28 | ); 29 | 30 | return new GetPagedSkillGroupsQueryResult($skillGroups, $pager); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/GetPagedSkillGroups/GetPagedSkillGroupsQueryResult.php: -------------------------------------------------------------------------------- 1 | pager); 24 | $paginator = $this->skillRepository->findByFilter($filter); 25 | $pager = new Pager($query->pager->page, $query->pager->perPage, $paginator->total); 26 | 27 | $skills = $this->skillDTOHydrator->fromSkillEntityList($paginator->items); 28 | 29 | return new GetPagedSkillsQueryResult($skills, $pager); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/GetPagedSkills/GetPagedSkillsQueryResult.php: -------------------------------------------------------------------------------- 1 | filter->publicationStatuses = [ 25 | PublicationStatus::PUBLISHED->value, 26 | ]; 27 | 28 | $pagination = $this->specialityRepository->findPagedItems($query->filter); 29 | $specialities = $this->specialityDTOHydrator->fromEntities($pagination->items); 30 | 31 | return new GetPagedSpecialitiesQueryResult( 32 | $specialities, 33 | new Pager( 34 | $query->filter->pager->page, 35 | $query->filter->pager->perPage, 36 | $pagination->total 37 | ) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Skills/Application/UseCase/Query/GetPagedSpecialities/GetPagedSpecialitiesQueryResult.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | private Collection $skills; 28 | 29 | private SkillGroupNameSpecification $skillGroupNameSpecification; 30 | private string $ownerId; 31 | 32 | public function __construct( 33 | string $name, 34 | string $ownerId, 35 | SkillGroupNameSpecification $skillGroupNameSpecification 36 | ) { 37 | $this->skillGroupNameSpecification = $skillGroupNameSpecification; 38 | $this->id = Id::makeUlid(); 39 | $this->skills = new ArrayCollection(); 40 | $this->setName($name); 41 | $this->ownerId = $ownerId; 42 | } 43 | 44 | public function setName(string $name): void 45 | { 46 | $this->name = $name; 47 | $this->skillGroupNameSpecification->satisfy($this); 48 | } 49 | 50 | public function getName(): string 51 | { 52 | return $this->name; 53 | } 54 | 55 | public function getId(): string 56 | { 57 | return $this->id; 58 | } 59 | 60 | /** 61 | * @return Collection 62 | */ 63 | public function getSkills(): Collection 64 | { 65 | return $this->skills; 66 | } 67 | 68 | public function getOwnerId(): string 69 | { 70 | return $this->ownerId; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/Skill/Specification/SkillGroupNameSpecification.php: -------------------------------------------------------------------------------- 1 | getName(), 2, 100); 20 | AssertService::true( 21 | empty($this->skillGroupRepository->findOneByName($skillGroup->getName())), 22 | 'Название группы должно быть уникальным' 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/Skill/Specification/SkillSpecification.php: -------------------------------------------------------------------------------- 1 | skillRepository->findByName($skill->getName()) as $foundSkill) { 19 | if ($skill->getId() === $foundSkill->getId()) { 20 | continue; 21 | } 22 | 23 | AssertService::notEq( 24 | $skill->getSkillGroup(), 25 | $foundSkill->getSkillGroup(), 26 | 'Навык с таким названием уже имеется в группе' 27 | ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/SkillConfirmation/Level.php: -------------------------------------------------------------------------------- 1 | id = Id::makeUlid(); 21 | $this->testId = $testId; 22 | $this->skillConfirmation = $skillConfirmation; 23 | } 24 | 25 | public function getId(): string 26 | { 27 | return $this->id; 28 | } 29 | 30 | public function getTestId(): string 31 | { 32 | return $this->testId; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/Specialist/Specialist.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | private Collection $specialties; 25 | 26 | public function __construct(string $publicUserId) 27 | { 28 | $this->id = Id::makeUlid(); 29 | $this->specialties = new ArrayCollection(); 30 | $this->publicUserId = $publicUserId; 31 | } 32 | 33 | public function addSpeciality(Speciality $speciality): void 34 | { 35 | if (!$this->specialties->contains($speciality)) { 36 | $this->specialties->add($speciality); 37 | } 38 | } 39 | 40 | public function getId(): string 41 | { 42 | return $this->id; 43 | } 44 | 45 | public function getPublicUserId(): ?string 46 | { 47 | return $this->publicUserId; 48 | } 49 | 50 | public function getSpecialties(): Collection 51 | { 52 | return $this->specialties; 53 | } 54 | 55 | public function setPublicUserId(?string $publicUserId): void 56 | { 57 | $this->publicUserId = $publicUserId; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/Speciality/Level.php: -------------------------------------------------------------------------------- 1 | value === $publicationStatus->value; 20 | } 21 | 22 | public function isDraft(): bool 23 | { 24 | return $this->equals(self::DRAFT); 25 | } 26 | 27 | public function isPublished(): bool 28 | { 29 | return $this->equals(self::PUBLISHED); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/Speciality/SpecialitySkill.php: -------------------------------------------------------------------------------- 1 | id = Id::makeUlid(); 23 | $this->speciality = $specialist; 24 | $this->skill = $skill; 25 | $this->level = $level; 26 | } 27 | 28 | public function setLevel(Level $level): void 29 | { 30 | $this->level = $level; 31 | } 32 | 33 | public function getId(): string 34 | { 35 | return $this->id; 36 | } 37 | 38 | public function getSpeciality(): Speciality 39 | { 40 | return $this->speciality; 41 | } 42 | 43 | public function getSkill(): Skill 44 | { 45 | return $this->skill; 46 | } 47 | 48 | public function getLevel(): Level 49 | { 50 | return $this->level; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Skills/Domain/Aggregate/Speciality/Specification/SpecialitySpecification.php: -------------------------------------------------------------------------------- 1 | skillSpecification); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Skills/Domain/Factory/SkillGroupFactory.php: -------------------------------------------------------------------------------- 1 | skillGroupNameSpecification 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Skills/Domain/Factory/SpecialistFactory.php: -------------------------------------------------------------------------------- 1 | specialitySpecification); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Skills/Domain/Repository/SkillConfirmationRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function findAllBySpecialityId(string $specialityId): array; 19 | 20 | public function findOne(string $id): ?SpecialitySkill; 21 | 22 | public function findOneBySpecialityAndSkill(string $specialityId, string $skillId): ?SpecialitySkill; 23 | } 24 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SkillFetcher.php: -------------------------------------------------------------------------------- 1 | skillRepository->findOneById($skillId); 20 | Assert::notNull($skill, 'Навык не найден'); 21 | 22 | return $skill; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SkillGroupMaker.php: -------------------------------------------------------------------------------- 1 | skillGroupFactory->create($name, $ownerId); 22 | $this->skillGroupRepository->add($skillGroup); 23 | 24 | return $skillGroup; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SkillMaker.php: -------------------------------------------------------------------------------- 1 | skillGroupRepository->findOneById($skillGroupId); 24 | $skill = $this->skillFactory->create($name, $skillGroup, $ownerId); 25 | $this->skillRepository->add($skill); 26 | 27 | return $skill; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SpecialistMaker.php: -------------------------------------------------------------------------------- 1 | specialistFactory->create($publicUserId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SpecialityFetcher.php: -------------------------------------------------------------------------------- 1 | specialityRepository->findOne( 21 | new SpecialityFilter( 22 | id: $id 23 | ) 24 | ); 25 | Assert::notEmpty($speciality, 'Специальность не найдена'); 26 | 27 | return $speciality; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SpecialityMaker.php: -------------------------------------------------------------------------------- 1 | specialityFactory->create($name, $ownerId); 22 | $this->specialityRepository->add($speciality); 23 | 24 | return $speciality; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Skills/Domain/Service/SpecialitySkillOrganizer.php: -------------------------------------------------------------------------------- 1 | skillFetcher->getRequiredSkill($skillId); 27 | $speciality = $this->specialityFetcher->getRequiredSpeciality($specialityId); 28 | 29 | $existingSpecialitySkill = $this->specialitySkillRepository 30 | ->findOneBySpecialityAndSkill($speciality->getId(), $skill->getId()); 31 | Assert::null($existingSpecialitySkill, 'Навык уже добавлен'); 32 | 33 | $specialitySkill = new SpecialitySkill($speciality, $skill, Level::from($level)); 34 | $this->specialitySkillRepository->add($specialitySkill); 35 | 36 | return $specialitySkill; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Api/SkillsApi.php: -------------------------------------------------------------------------------- 1 | commandInteractor->confirmSpecialistSkill( 25 | new ConfirmSpecialistSkillCommand( 26 | $skillId, $userId, $testId, $correctAnswersPercentage 27 | ) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Console/TestCommand.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Database/ORM/Aggregate/Skill.SkillGroup.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Database/ORM/Aggregate/SkillConfirmation.Proof.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Database/ORM/Aggregate/SkillConfirmation.SkillConfirmation.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Database/ORM/Aggregate/Specialist.Specialist.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Database/ORM/Aggregate/Speciality.Speciality.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Database/ORM/Aggregate/Speciality.SpecialitySkill.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Repository/SkillConfirmationRepository.php: -------------------------------------------------------------------------------- 1 | findOneBy(['skill' => $skillId, 'specialist' => $specialistId]); 20 | } 21 | 22 | public function add(SkillConfirmation $skillConfirmation): void 23 | { 24 | $this->_em->persist($skillConfirmation); 25 | $this->_em->flush(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Repository/SkillGroupRepository.php: -------------------------------------------------------------------------------- 1 | findOneBy(['name' => $name]); 23 | } 24 | 25 | public function add(SkillGroup $entity): void 26 | { 27 | $this->_em->persist($entity); 28 | $this->_em->flush(); 29 | } 30 | 31 | public function findOneById(string $id): ?SkillGroup 32 | { 33 | return $this->find($id); 34 | } 35 | 36 | public function findByFilter(SkillGroupsFilter $filter): PaginationResult 37 | { 38 | $qb = $this->createQueryBuilder('sg'); 39 | 40 | if ($filter->name) { 41 | $qb->where($qb->expr()->like('sg.name', ':name')) 42 | ->setParameter('name', '%'.$filter->name.'%'); 43 | } 44 | 45 | if ($filter->pager) { 46 | $qb->setMaxResults($filter->pager->getLimit()); 47 | $qb->setFirstResult($filter->pager->getOffset()); 48 | } 49 | 50 | $paginator = new Paginator($qb->getQuery()); 51 | 52 | return new PaginationResult(iterator_to_array($paginator->getIterator()), $paginator->count()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Repository/SpecialistRepository.php: -------------------------------------------------------------------------------- 1 | findOneBy(['publicUserId' => $publicUserId]); 20 | } 21 | 22 | public function add(Specialist $specialist): void 23 | { 24 | $this->_em->persist($specialist); 25 | $this->_em->flush(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Skills/Infrastructure/Repository/SpecialitySkillRepository.php: -------------------------------------------------------------------------------- 1 | _em->persist($entity); 22 | $this->_em->flush(); 23 | } 24 | 25 | public function remove(SpecialitySkill $entity): void 26 | { 27 | $this->_em->remove($entity); 28 | $this->_em->flush(); 29 | } 30 | 31 | public function findAllBySpecialityId(string $specialityId): array 32 | { 33 | return $this->findBy(['speciality' => $specialityId]); 34 | } 35 | 36 | public function findOne(string $id): ?SpecialitySkill 37 | { 38 | return $this->find($id); 39 | } 40 | 41 | public function findOneBySpecialityAndSkill(string $specialityId, string $skillId): ?SpecialitySkill 42 | { 43 | return $this->findOneBy(['speciality' => $specialityId, 'skill' => $skillId]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Testing/Application/Command/CreateAnswerOption/CreateAnswerOptionCommand.php: -------------------------------------------------------------------------------- 1 | questionRepository->findOneById($command->questionId); 23 | Assert::notEmpty($question, 'Тест не найден'); 24 | $answerOption = $this->answerOptionFactory->create($question, $command->description, $command->correct); 25 | $question->addAnswerOption($answerOption); 26 | $this->questionRepository->add($question); 27 | 28 | return $answerOption->getId(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Testing/Application/Command/CreateQuestion/CreateQuestionCommand.php: -------------------------------------------------------------------------------- 1 | testRepository->findOneById($command->testId); 23 | $question = $this->questionFactory->create( 24 | $command->name, 25 | $command->description, 26 | QuestionType::from($command->type), 27 | $test 28 | ); 29 | $test->addQuestion($question); 30 | $this->testRepository->add($test); 31 | 32 | return $question->getId(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Testing/Application/Command/CreateTest/CreateTestCommand.php: -------------------------------------------------------------------------------- 1 | testFactory->create( 26 | $command->ownerId, 27 | $command->name, 28 | $command->description, 29 | DifficultyLevel::from($command->difficultyLevel), 30 | $command->correctAnswersPercentage, 31 | $command->skillId, 32 | ); 33 | $this->testRepository->add($test); 34 | 35 | return $test->getId(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Testing/Application/Command/CreateTestingSession/CreateTestingSessionCommand.php: -------------------------------------------------------------------------------- 1 | getId(), 22 | description: $answerOption->getDescription(), 23 | isCorrect: $answerOption->isCorrect(), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/DTO/Test/QuestionDTO.php: -------------------------------------------------------------------------------- 1 | AnswerOptionDTO::fromEntity($a), 30 | $question->getAnswerOptions()->toArray() 31 | ); 32 | 33 | return new self( 34 | id: $question->getId(), 35 | name: $question->getName(), 36 | description: $question->getDescription(), 37 | published: $question->isPublished(), 38 | type: $question->getType()->value, 39 | positionNumber: $question->getPositionNumber(), 40 | answerOptions: $answerOptions, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/DTO/Test/TestDTO.php: -------------------------------------------------------------------------------- 1 | QuestionDTO::fromEntity($question), 32 | $test->getQuestions()->toArray() 33 | ); 34 | 35 | return new self( 36 | id: $test->getId(), 37 | creatorId: $test->getOwnerId(), 38 | name: $test->getName(), 39 | description: $test->getDescription(), 40 | difficultyLevel: $test->getDifficultyLevel()->value, 41 | questions: $questions, 42 | correctAnswersPercentage: $test->getCorrectAnswersPercentage(), 43 | skillId: $test->getSkillId(), 44 | isPublished: $test->published(), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/DTO/TestingSessionDTO.php: -------------------------------------------------------------------------------- 1 | getId(), 22 | $testingSession->getUserId(), 23 | $testingSession->getTest()->getId(), 24 | $testingSession->getCorrectAnswersPercentage(), 25 | $testingSession->getTest()->getSkillId() 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/FindAnswerOption/FindAnswerOptionQuery.php: -------------------------------------------------------------------------------- 1 | answerOptionRepository->findOneById($query->answerOptionId); 20 | 21 | return new FindAnswerOptionQueryResult( 22 | $answerOption ? AnswerOptionDTO::fromEntity($answerOption) : null 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/FindAnswerOption/FindAnswerOptionQueryResult.php: -------------------------------------------------------------------------------- 1 | questionRepository->findOneById($query->id); 20 | 21 | return new FindQuestionQueryResult($question ? QuestionDTO::fromEntity($question) : null); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/FindQuestion/FindQuestionQueryResult.php: -------------------------------------------------------------------------------- 1 | testRepository->findOneById($query->id); 20 | 21 | return new FindTestQueryResult($test ? TestDTO::fromEntity($test) : null); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/FindTest/FindTestQueryResult.php: -------------------------------------------------------------------------------- 1 | testingSessionRepository->findById($query->testingSessionId); 18 | 19 | return new FindTestingSessionQueryResult( 20 | $testingSession ? TestingSessionDTO::fromEntity($testingSession) : null 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Testing/Application/Query/FindTestingSession/FindTestingSessionQueryResult.php: -------------------------------------------------------------------------------- 1 | queryBus->execute($query); 26 | } 27 | 28 | public function findQuestion(FindQuestionQuery $query): FindQuestionQueryResult 29 | { 30 | return $this->queryBus->execute($query); 31 | } 32 | 33 | public function findAnswerOption(FindAnswerOptionQuery $query): FindAnswerOptionQueryResult 34 | { 35 | return $this->queryBus->execute($query); 36 | } 37 | 38 | public function findTestingSession(string $testingSessionId): FindTestingSessionQueryResult 39 | { 40 | return $this->queryBus->execute(new FindTestingSessionQuery($testingSessionId)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Testing/Domain/Aggregate/Test/AnswerOption.php: -------------------------------------------------------------------------------- 1 | id = UlidService::generate(); 28 | $this->question = $question; 29 | $this->description = $description; 30 | $this->correct = $isCorrect; 31 | } 32 | 33 | public function isCorrect(): bool 34 | { 35 | return $this->correct; 36 | } 37 | 38 | public function getId(): string 39 | { 40 | return $this->id; 41 | } 42 | 43 | public function getQuestion(): Question 44 | { 45 | return $this->question; 46 | } 47 | 48 | public function getDescription(): string 49 | { 50 | return $this->description; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Testing/Domain/Aggregate/Test/DifficultyLevel.php: -------------------------------------------------------------------------------- 1 | id = UlidService::generate(); 24 | $this->testingSession = $testingSession; 25 | $this->question = $question; 26 | } 27 | 28 | public function getQuestion(): Question 29 | { 30 | return $this->question; 31 | } 32 | 33 | public function isCorrect(): bool 34 | { 35 | foreach ($this->question->getAnswerOptions() as $answerOption) { 36 | if ($answerOption->isCorrect() && in_array($answerOption->getId(), $this->answeredOptions, true)) { 37 | return true; 38 | } 39 | } 40 | 41 | return false; 42 | } 43 | 44 | public function addAnswerOption(AnswerOption $answerOption): void 45 | { 46 | $this->answeredOptions[$answerOption->getId()] = $answerOption->getId(); 47 | } 48 | 49 | public function getId(): string 50 | { 51 | return $this->id; 52 | } 53 | 54 | public function getTestingSession(): TestingSession 55 | { 56 | return $this->testingSession; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Testing/Domain/Event/TestingSessionCompletedEvent.php: -------------------------------------------------------------------------------- 1 | testSpecification); 26 | $test->setCorrectAnswersPercentage($correctAnswersPercentage); 27 | 28 | if ($skillId) { 29 | $test->setSkillId($skillId); 30 | } 31 | 32 | return $test; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Testing/Domain/Factory/TestingSessionFactory.php: -------------------------------------------------------------------------------- 1 | testRepository->findOneById($testId); 24 | $entity = $this->testingSessionFactory->create($test, $userId); 25 | $this->testingSessionRepository->add($entity); 26 | 27 | return $entity; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Testing/Domain/Specification/TestSpecification.php: -------------------------------------------------------------------------------- 1 | testRepository->findByName($test->getName()) as $existingTest) { 21 | AssertService::notEq( 22 | $existingTest->getName(), 23 | $test->getName(), 24 | 'Тест с таким названием уже существует' 25 | ); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Adapter/SkillsAdapter.php: -------------------------------------------------------------------------------- 1 | api->confirmSpecialistSkill($skillId, $userId, $testId, $correctAnswersPercentage); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Adapter/SkillsApiInterface.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Database/ORM/Aggregate/Test.Question.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Database/ORM/Aggregate/Test.Test.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Database/ORM/Aggregate/TestingSession.TestingSession.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Database/ORM/Aggregate/TestingSession.UserAnswer.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/EventHandler/TestingSessionCompletedEventHandler.php: -------------------------------------------------------------------------------- 1 | queryInteractor 21 | ->findTestingSession($event->testingSessionId) 22 | ->testingSession; 23 | 24 | $this->skillsAdapter->confirmSpecialistSkill( 25 | $testingSession->skillId, 26 | $testingSession->userId, 27 | $testingSession->testId, 28 | $testingSession->correctAnswersPercentage 29 | ); 30 | 31 | return $testingSession->id; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Repository/AnswerOptionRepository.php: -------------------------------------------------------------------------------- 1 | find($answerOptionId); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Repository/QuestionRepository.php: -------------------------------------------------------------------------------- 1 | find($id); 22 | } 23 | 24 | public function add(Question $entity): void 25 | { 26 | $this->_em->persist($entity); 27 | $this->_em->flush(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Repository/TestRepository.php: -------------------------------------------------------------------------------- 1 | _em->persist($entity); 22 | $this->_em->flush(); 23 | } 24 | 25 | public function findOneById(string $id): ?Test 26 | { 27 | return $this->find($id); 28 | } 29 | 30 | /** 31 | * @return Test[] 32 | */ 33 | public function findBySkill(string $skillId): array 34 | { 35 | return $this->findBy(['skillId' => $skillId]); 36 | } 37 | 38 | public function findByName(string $name): array 39 | { 40 | return $this->findBy(['name' => $name]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Testing/Infrastructure/Repository/TestingSessionRepository.php: -------------------------------------------------------------------------------- 1 | find($id); 20 | } 21 | 22 | public function add(TestingSession $testingSession): void 23 | { 24 | $this->_em->persist($testingSession); 25 | $this->_em->flush(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Users/Application/DTO/UserDTO.php: -------------------------------------------------------------------------------- 1 | getId(), name: '', email: $user->getEmail()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/AdminUseCaseInteractor.php: -------------------------------------------------------------------------------- 1 | commandBus->execute($command); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/Command/CreateUser/CreateUserCommand.php: -------------------------------------------------------------------------------- 1 | userFactory->create($createUserCommand->email, $createUserCommand->password); 23 | $this->userRepository->add($user); 24 | 25 | return $user->getId(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/PrivateUseCaseInteractor.php: -------------------------------------------------------------------------------- 1 | queryBus->execute(new GetMeQuery()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/PublicUseCaseInteractor.php: -------------------------------------------------------------------------------- 1 | queryBus->execute($query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/Query/FindUser/FindUserQuery.php: -------------------------------------------------------------------------------- 1 | userRepository->findById($query->userId); 20 | $userDTO = $user ? UserDTO::fromEntity($user) : null; 21 | 22 | return new FindUserQueryResult($userDTO); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/Query/FindUser/FindUserQueryResult.php: -------------------------------------------------------------------------------- 1 | userRepository->findByEmail($query->email); 20 | 21 | if (!$user) { 22 | throw new \Exception('User not found'); 23 | } 24 | 25 | return UserDTO::fromEntity($user); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/Query/GetMe/GetMeQuery.php: -------------------------------------------------------------------------------- 1 | userRepository->findById($this->userFetcher->requiredUser()->getId()); 23 | $userDTO = $user ? UserDTO::fromEntity($user) : null; 24 | 25 | return new GetMeQueryResult($userDTO); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Users/Application/UseCase/Query/GetMe/GetMeQueryResult.php: -------------------------------------------------------------------------------- 1 | setPassword($password, $this->passwordHasher); 21 | 22 | return $user; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Users/Domain/Repository/UserRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | userFetcher->requiredUser(); 23 | 24 | return new JsonResponse([ 25 | 'ulid' => $user->getId(), 26 | 'email' => $user->getEmail(), 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Users/Infrastructure/Database/ORM/RefreshToken.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Users/Infrastructure/Database/ORM/User.orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Users/Infrastructure/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | _em->persist($user); 22 | $this->_em->flush(); 23 | } 24 | 25 | public function findById(string $ulid): ?User 26 | { 27 | return $this->find($ulid); 28 | } 29 | 30 | public function findByEmail(string $email): ?User 31 | { 32 | return $this->findOneBy(['email' => $email]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Users/Infrastructure/UserPasswordHasher.php: -------------------------------------------------------------------------------- 1 | passwordHasher->hashPassword($user, $password); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Functional/Api/ApiTestCase.php: -------------------------------------------------------------------------------- 1 | client = static::createClient(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Functional/Api/AuthApiTestCase.php: -------------------------------------------------------------------------------- 1 | authenticate(); 27 | } 28 | 29 | public function authenticate(): void 30 | { 31 | $this->user = $this->loadUserFixture(); 32 | $jwtEncoder = $this->getService(JWTEncoderInterface::class); 33 | $this->client->setServerParameter( 34 | 'HTTP_AUTHORIZATION', 35 | sprintf( 36 | 'Bearer %s', 37 | $jwtEncoder->encode([ 38 | 'email' => $this->user->getEmail(), 39 | ] 40 | ) 41 | ) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Functional/Shared/Infrastructure/Controller/HealthCheckActionTest.php: -------------------------------------------------------------------------------- 1 | request(Request::METHOD_GET, '/health-check'); 15 | 16 | $this->assertResponseIsSuccessful(); 17 | $jsonResult = json_decode($client->getResponse()->getContent(), true); 18 | $this->assertEquals($jsonResult['status'], 'ok'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Functional/Users/Application/Command/CreateUser/CreateUserCommandHandlerTest.php: -------------------------------------------------------------------------------- 1 | commandBus = $this->getService(CommandBusInterface::class); 24 | $this->userRepository = $this->getService(UserRepositoryInterface::class); 25 | } 26 | 27 | public function test_user_created_successfully(): void 28 | { 29 | $command = new CreateUserCommand($this->getFaker()->email(), $this->getFaker()->password()); 30 | 31 | // act 32 | $userUlid = $this->commandBus->execute($command); 33 | 34 | // assert 35 | $user = $this->userRepository->findById($userUlid); 36 | $this->assertNotEmpty($user); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Functional/Users/Application/Query/FindUserByEmail/FindUserByEmailQueryHandlerTest.php: -------------------------------------------------------------------------------- 1 | queryBus = $this->getService(QueryBusInterface::class); 26 | $this->databaseTool = $this->getService(DatabaseToolCollection::class)->get(); 27 | } 28 | 29 | public function test_user_created_when_command_executed(): void 30 | { 31 | // arrange 32 | $referenceRepository = $this->databaseTool->loadFixtures([UserFixture::class])->getReferenceRepository(); 33 | /** @var User $user */ 34 | $user = $referenceRepository->getReference(UserFixture::REFERENCE); 35 | $query = new FindUserByEmailQuery($user->getEmail()); 36 | 37 | // act 38 | $userDTO = $this->queryBus->execute($query); 39 | 40 | // assert 41 | $this->assertInstanceOf(UserDTO::class, $userDTO); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Resource/Fixture/Skills/SkillFixture.php: -------------------------------------------------------------------------------- 1 | getReference(SkillGroupFixture::REFERENCE); 29 | $skill = $this->skillFactory->create($this->getFaker()->name(), $skillGroup, $skillGroup->getOwnerId()); 30 | 31 | $manager->persist($skill); 32 | $manager->flush(); 33 | 34 | $this->addReference(self::REFERENCE, $skill); 35 | } 36 | 37 | public function getDependencies(): array 38 | { 39 | return [ 40 | SkillGroupFixture::class, 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Resource/Fixture/Skills/SkillGroupFixture.php: -------------------------------------------------------------------------------- 1 | getReference(UserFixture::REFERENCE); 30 | $skillGroup = $this->skillGroupFactory->create($this->getFaker()->name(), $user->getId()); 31 | 32 | $manager->persist($skillGroup); 33 | $manager->flush(); 34 | 35 | $this->addReference(self::REFERENCE, $skillGroup); 36 | } 37 | 38 | public function getDependencies(): array 39 | { 40 | return [ 41 | UserFixture::class, 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Resource/Fixture/Users/UserFixture.php: -------------------------------------------------------------------------------- 1 | getFaker()->email(); 25 | 26 | $password = $this->getFaker()->password(); 27 | $user = $this->userFactory->create($email, $password); 28 | 29 | $manager->persist($user); 30 | $manager->flush(); 31 | 32 | $this->addReference(self::REFERENCE, $user); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Tools/DITools.php: -------------------------------------------------------------------------------- 1 | $service 15 | * 16 | * @return T|object|null 17 | */ 18 | public function getService(string $service) 19 | { 20 | return static::getContainer()->get($service); 21 | } 22 | 23 | public function getUrlGenerator(): UrlGeneratorInterface 24 | { 25 | return $this->getService(UrlGeneratorInterface::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Tools/FakerTools.php: -------------------------------------------------------------------------------- 1 | get(DatabaseToolCollection::class)->get(); 19 | } 20 | 21 | public function loadUserFixture(): User 22 | { 23 | $executor = $this->getDatabaseTools()->loadFixtures([UserFixture::class], true); 24 | /** @var User $user */ 25 | $user = $executor->getReferenceRepository()->getReference(UserFixture::REFERENCE); 26 | 27 | return $user; 28 | } 29 | 30 | public function loadSkillGroupFixture(): SkillGroup 31 | { 32 | $executor = $this->getDatabaseTools()->loadFixtures([SkillGroupFixture::class], true); 33 | 34 | return $executor->getReferenceRepository()->getReference(SkillGroupFixture::REFERENCE); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 11 | } 12 | --------------------------------------------------------------------------------