├── .docker ├── otel │ └── otel-collector-config.yml ├── php │ └── Dockerfile └── temporalio │ ├── development-cass.yaml │ ├── development-sql.yaml │ └── docker.yaml ├── .env ├── .idea ├── inspectionProfiles │ └── Project_Default.xml ├── microservices.iml ├── php.xml ├── phpunit.xml ├── shelf │ ├── Changes.xml │ └── Changes │ │ ├── favicon.ico │ │ └── shelved.patch ├── vcs.xml └── workspace.xml ├── Makefile ├── README.md ├── docker-compose.yaml ├── lib ├── grpc-shared │ ├── .gitignore │ ├── bin │ │ ├── console │ │ └── protoc-gen-php-grpc │ ├── composer.json │ ├── composer.lock │ ├── generated │ │ └── GRPC │ │ │ ├── ProtobufMetadata │ │ │ ├── Auth │ │ │ │ └── v1 │ │ │ │ │ ├── Message.php │ │ │ │ │ ├── Request.php │ │ │ │ │ ├── Response.php │ │ │ │ │ └── Service.php │ │ │ ├── Common │ │ │ │ └── v1 │ │ │ │ │ └── Message.php │ │ │ ├── Payment │ │ │ │ └── v1 │ │ │ │ │ ├── Message.php │ │ │ │ │ ├── Request.php │ │ │ │ │ ├── Response.php │ │ │ │ │ └── Service.php │ │ │ └── Users │ │ │ │ └── v1 │ │ │ │ ├── Message.php │ │ │ │ ├── Request.php │ │ │ │ ├── Response.php │ │ │ │ └── Service.php │ │ │ └── Services │ │ │ ├── Auth │ │ │ └── v1 │ │ │ │ ├── AuthServiceClient.php │ │ │ │ ├── AuthServiceInterface.php │ │ │ │ ├── LoginRequest.php │ │ │ │ ├── LoginResponse.php │ │ │ │ ├── LogoutRequest.php │ │ │ │ ├── MeRequest.php │ │ │ │ ├── MeResponse.php │ │ │ │ ├── RegisterRequest.php │ │ │ │ ├── RegisterResponse.php │ │ │ │ └── Token.php │ │ │ ├── Common │ │ │ └── v1 │ │ │ │ ├── Exception.php │ │ │ │ └── PBEmpty.php │ │ │ ├── Payment │ │ │ └── v1 │ │ │ │ ├── ChargeRequest.php │ │ │ │ ├── ChargeResponse.php │ │ │ │ ├── Money.php │ │ │ │ ├── Payment.php │ │ │ │ ├── PaymentServiceClient.php │ │ │ │ ├── PaymentServiceInterface.php │ │ │ │ └── Receipt.php │ │ │ └── Users │ │ │ └── v1 │ │ │ ├── CreateRequest.php │ │ │ ├── CreateRequest │ │ │ └── User.php │ │ │ ├── CreateRequest_User.php │ │ │ ├── CreateResponse.php │ │ │ ├── GetRequest.php │ │ │ ├── GetResponse.php │ │ │ ├── UpdateRequest.php │ │ │ ├── UpdateRequest │ │ │ └── User.php │ │ │ ├── UpdateRequest_User.php │ │ │ ├── UpdateResponse.php │ │ │ ├── User.php │ │ │ ├── UsersServiceClient.php │ │ │ └── UsersServiceInterface.php │ ├── generator │ │ ├── CommandExecutor.php │ │ ├── Console │ │ │ └── GeneratorCommand.php │ │ ├── Exception │ │ │ └── CompileException.php │ │ ├── Generators │ │ │ ├── BootloaderGenerator.php │ │ │ ├── Command │ │ │ │ ├── Annotation │ │ │ │ │ ├── DefaultValue.php │ │ │ │ │ ├── DocType.php │ │ │ │ │ ├── Event.php │ │ │ │ │ ├── Guarded.php │ │ │ │ │ ├── Internal.php │ │ │ │ │ ├── Optional.php │ │ │ │ │ └── Type.php │ │ │ │ ├── ClassPropertiesGenerator.php │ │ │ │ ├── JsonSerializationGenerator.php │ │ │ │ └── PropertyType.php │ │ │ ├── CommandClassGenerator.php │ │ │ ├── ConfigGenerator.php │ │ │ ├── EnumClassGenerator.php │ │ │ ├── GeneratedMessagesFixer.php │ │ │ ├── GeneratorInterface.php │ │ │ ├── Message │ │ │ │ ├── MessageClass.php │ │ │ │ ├── MessageClassParser.php │ │ │ │ └── MessageCommentsParser.php │ │ │ ├── ServiceClientGenerator.php │ │ │ ├── ServiceClientTestsGenerator.php │ │ │ └── ServiceInterfaceAttributesGenerator.php │ │ ├── PHP │ │ │ ├── AnnotationsParser.php │ │ │ ├── AttributesParser.php │ │ │ ├── ClassDeclaration.php │ │ │ ├── ClassDeclarationFactory.php │ │ │ ├── ClassTransformer.php │ │ │ └── Property │ │ │ │ ├── BuiltInType.php │ │ │ │ ├── ClassType.php │ │ │ │ ├── DateTime.php │ │ │ │ ├── EnumClassType.php │ │ │ │ ├── Interval.php │ │ │ │ ├── RepeatableType.php │ │ │ │ ├── Timestamp.php │ │ │ │ ├── Type.php │ │ │ │ └── TypeFactory.php │ │ ├── ProtoCompiler.php │ │ └── ProtocCommandBuilder.php │ └── src │ │ ├── Attribute │ │ ├── Guarded.php │ │ └── Internal.php │ │ ├── Bootloader │ │ └── ServiceBootloader.php │ │ ├── Config │ │ └── GRPCServicesConfig.php │ │ ├── Interceptors │ │ ├── Incoming │ │ │ ├── ContextInterceptor.php │ │ │ ├── ExceptionHandlerInterceptor.php │ │ │ └── OpenTelemetryInterceptor.php │ │ └── Outgoing │ │ │ └── SendTraceContextInterceptor.php │ │ └── Request │ │ └── RequestContext.php └── temporal-shared │ ├── .gitignore │ ├── composer.json │ ├── composer.lock │ └── src │ ├── Activity │ └── NotificationsActivity.php │ ├── Bootloader │ └── TemporalSharedBootloader.php │ ├── OptionsTrait.php │ ├── TaskQueue.php │ └── Workflow │ ├── EmailVerificationWorkflow.php │ ├── ExceptionHelper.php │ ├── KYCWorkflow.php │ ├── RegisterUserWorkflow.php │ └── SubscriptionWorkflow.php ├── notifications ├── .editorconfig ├── .env.sample ├── .gitignore ├── .rr-prod.yaml ├── .rr.yaml ├── .styleci.yml ├── README.md ├── app.php ├── app │ ├── config │ │ ├── cycle.php │ │ ├── database.php │ │ ├── migration.php │ │ ├── scaffolder.php │ │ └── sentry.php │ ├── migrations │ │ └── .gitignore │ └── src │ │ └── Application │ │ ├── Assert.php │ │ ├── Bootloader │ │ ├── AppBootloader.php │ │ ├── ExceptionHandlerBootloader.php │ │ └── PersistenceBootloader.php │ │ ├── Exception │ │ └── InvalidArgumentException.php │ │ └── Kernel.php ├── composer.json ├── composer.lock ├── phpunit.xml ├── psalm.xml └── tests │ ├── App │ └── TestKernel.php │ ├── Feature │ └── .gitignore │ ├── TestCase.php │ └── Unit │ └── DemoTest.php ├── proto ├── auth │ └── v1 │ │ ├── message.proto │ │ ├── request.proto │ │ ├── response.proto │ │ └── service.proto ├── common │ └── v1 │ │ └── message.proto ├── payment │ └── v1 │ │ ├── message.proto │ │ ├── request.proto │ │ ├── response.proto │ │ └── service.proto └── users │ └── v1 │ ├── message.proto │ ├── request.proto │ ├── response.proto │ └── service.proto ├── subscriptions ├── .editorconfig ├── .env.sample ├── .gitignore ├── .rr-prod.yaml ├── .rr.yaml ├── .styleci.yml ├── README.md ├── app.php ├── app │ ├── config │ │ ├── cycle.php │ │ ├── database.php │ │ ├── migration.php │ │ ├── scaffolder.php │ │ └── sentry.php │ ├── migrations │ │ ├── .gitignore │ │ └── 20240708.163856_0_0_default_create_subscriptions.php │ └── src │ │ ├── Application │ │ ├── Assert.php │ │ ├── Bootloader │ │ │ ├── AppBootloader.php │ │ │ ├── ExceptionHandlerBootloader.php │ │ │ └── PersistenceBootloader.php │ │ ├── Exception │ │ │ └── InvalidArgumentException.php │ │ └── Kernel.php │ │ ├── Domain │ │ └── Subscription │ │ │ ├── Subscription.php │ │ │ ├── SubscriptionRepositoryInterface.php │ │ │ └── ValueObject │ │ │ └── Uuid.php │ │ └── Endpoint │ │ ├── Console │ │ └── SeedSubscriptionsCommand.php │ │ └── Temporal │ │ └── SubscriptionWorkflow.php ├── composer.json ├── composer.lock ├── phpunit.xml ├── psalm.xml └── tests │ ├── App │ └── TestKernel.php │ ├── Feature │ └── .gitignore │ ├── TestCase.php │ └── Unit │ └── DemoTest.php ├── users ├── .editorconfig ├── .env.sample ├── .gitignore ├── .rr-prod.yaml ├── .rr.otel.yaml ├── .rr.yaml ├── .rr │ ├── .rr.grpc.yaml │ └── .rr.otel.yaml ├── .styleci.yml ├── README.md ├── app.php ├── app │ ├── config │ │ ├── cycle.php │ │ ├── database.php │ │ ├── grpc.php │ │ ├── grpcServices.php │ │ ├── migration.php │ │ ├── scaffolder.php │ │ └── sentry.php │ ├── migrations │ │ ├── .gitignore │ │ ├── 20240529.182720_0_0_default_create_users.php │ │ ├── 20240529.183200_0_0_default_change_users_add_fist_name_add_last_name_add_language.php │ │ ├── 20240529.191748_0_0_default_change_users_add_created_at_add_updated_at.php │ │ ├── 20240604.183640_0_0_default_create_auth_tokens.php │ │ └── 20240708.150058_0_0_default_change_users_add_email_verified_at_add_kyc_verified_at_add_subscription_uuid_alter_updated_at.php │ └── src │ │ ├── Application │ │ ├── Assert.php │ │ ├── Bootloader │ │ │ ├── AppBootloader.php │ │ │ ├── AuthBootloader.php │ │ │ ├── ExceptionHandlerBootloader.php │ │ │ ├── PersistenceBootloader.php │ │ │ └── UserBootloader.php │ │ ├── Exception │ │ │ ├── AuthException.php │ │ │ ├── EmailAlreadyExistsException.php │ │ │ ├── InvalidArgumentException.php │ │ │ ├── PasswordIncorrectException.php │ │ │ └── UserNotFoundException.php │ │ ├── Kernel.php │ │ ├── PasswordHasher.php │ │ ├── Security │ │ │ └── OrmActorProvider.php │ │ └── UserService.php │ │ ├── Domain │ │ └── User │ │ │ ├── PasswordHasherInterface.php │ │ │ ├── Profile.php │ │ │ ├── Specification │ │ │ └── UniqueEmailSpecificationInterface.php │ │ │ ├── User.php │ │ │ ├── UserFactoryInterface.php │ │ │ ├── UserRepositoryInterface.php │ │ │ ├── UserServiceInterface.php │ │ │ └── ValueObject │ │ │ ├── Email.php │ │ │ ├── Password.php │ │ │ ├── PasswordHash.php │ │ │ └── Uuid.php │ │ ├── Endpoint │ │ ├── Console │ │ │ └── RegisterUserCommand.php │ │ ├── GRPC │ │ │ ├── Exception │ │ │ │ └── UnauthorizedException.php │ │ │ ├── Interceptor │ │ │ │ ├── GuardInterceptor.php │ │ │ │ └── HandleExceptionsInterceptor.php │ │ │ ├── Mapper │ │ │ │ ├── TimestampMapper.php │ │ │ │ └── UserService │ │ │ │ │ └── UserMapper.php │ │ │ └── Service │ │ │ │ ├── AuthService.php │ │ │ │ └── UserService.php │ │ └── Temporal │ │ │ └── User │ │ │ ├── CreditApplicationWorkflow.php │ │ │ ├── EmailVerificationWorkflow.php │ │ │ ├── KYCWorkflow.php │ │ │ ├── RegisterUserWorkflow.php │ │ │ ├── RegisterWorkflow.php │ │ │ ├── SubscriptionWorkflow.php │ │ │ └── UserServiceActivity.php │ │ └── Infrastructure │ │ └── CycleOrm │ │ ├── Factory │ │ └── UserFactory.php │ │ ├── Repository │ │ └── UserRepository.php │ │ └── Specification │ │ └── UniqueEmailSpecification.php ├── composer.json ├── composer.lock ├── phpunit.xml ├── psalm.xml └── tests │ ├── App │ └── TestKernel.php │ ├── Feature │ └── .gitignore │ ├── TestCase.php │ └── Unit │ └── DemoTest.php └── web ├── .editorconfig ├── .env.sample ├── .gitignore ├── .rr-prod.yaml ├── .rr.yaml ├── .styleci.yml ├── README.md ├── app.php ├── app ├── config │ ├── grpc-services.php │ ├── grpc.php │ ├── scaffolder.php │ └── session.php ├── src │ ├── Application │ │ ├── Auth │ │ │ ├── AuthKey.php │ │ │ └── AuthKeyInterface.php │ │ ├── Bootloader │ │ │ ├── AppBootloader.php │ │ │ ├── ExceptionHandlerBootloader.php │ │ │ ├── LoggingBootloader.php │ │ │ └── RoutesBootloader.php │ │ ├── Exception │ │ │ └── NotFoundException.php │ │ ├── GRPC │ │ │ └── Interceptor │ │ │ │ └── AuthInterceptor.php │ │ └── Kernel.php │ └── Endpoint │ │ ├── Console │ │ └── TestCommand.php │ │ └── Http │ │ ├── Controller │ │ ├── Auth │ │ │ └── LoginAction.php │ │ └── User │ │ │ ├── CreateAction.php │ │ │ └── ShowAction.php │ │ ├── Middleware │ │ ├── ErrorHandlerMiddleware.php │ │ └── SimpleAuthMiddleware.php │ │ └── Request │ │ ├── Auth │ │ └── LoginRequest.php │ │ └── User │ │ └── CreateRequest.php └── views │ └── home.php ├── composer.json ├── composer.lock ├── functions.php ├── phpunit.xml ├── psalm.xml ├── public ├── favicon.ico ├── images │ └── logo.svg └── styles │ └── welcome.css └── tests ├── App └── TestKernel.php ├── Feature └── .gitignore ├── TestCase.php └── Unit └── DemoTest.php /.docker/otel/otel-collector-config.yml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | http: 6 | 7 | processors: 8 | batch: 9 | timeout: 1s 10 | 11 | exporters: 12 | zipkin: 13 | endpoint: "http://ms-zipkin:9411/api/v2/spans" 14 | 15 | service: 16 | pipelines: 17 | traces: 18 | receivers: [ otlp ] 19 | processors: [ batch ] 20 | exporters: [ zipkin ] 21 | -------------------------------------------------------------------------------- /.docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/roadrunner-server/roadrunner:2023.3.12 as rr 2 | 3 | FROM ghcr.io/spiral/php-grpc:8.2 as backend 4 | 5 | RUN apk add --no-cache \ 6 | openssh-client \ 7 | ca-certificates \ 8 | postgresql-dev 9 | 10 | RUN docker-php-ext-install \ 11 | pgsql pdo_pgsql 12 | 13 | RUN curl -s https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer 14 | 15 | ARG SERVICE_NAME 16 | ARG APP_VERSION=v1.0 17 | ENV COMPOSER_ALLOW_SUPERUSER=1 18 | ENV SERVICE_NAME=php-$SERVICE_NAME 19 | 20 | COPY --from=rr /usr/bin/rr /bin 21 | 22 | WORKDIR /app 23 | 24 | #RUN composer config --no-plugins allow-plugins.spiral/composer-publish-plugin false 25 | #RUN composer install --no-dev --no-interaction --no-progress --no-suggest --optimize-autoloader 26 | 27 | CMD ["/bin/rr", "serve", "-e", "-c", "/app/.rr-prod.yaml"] 28 | -------------------------------------------------------------------------------- /.docker/temporalio/development-cass.yaml: -------------------------------------------------------------------------------- 1 | system.forceSearchAttributesCacheRefreshOnRead: 2 | - value: true # Dev setup only. Please don't turn this on in production. 3 | constraints: {} 4 | -------------------------------------------------------------------------------- /.docker/temporalio/development-sql.yaml: -------------------------------------------------------------------------------- 1 | limit.maxIDLength: 2 | - value: 255 3 | constraints: {} 4 | system.forceSearchAttributesCacheRefreshOnRead: 5 | - value: true # Dev setup only. Please don't turn this on in production. 6 | constraints: {} 7 | -------------------------------------------------------------------------------- /.docker/temporalio/docker.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/.docker/temporalio/docker.yaml -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | DEBUG=true 3 | VERBOSITY_LEVEL=verbose # basic, verbose, debug 4 | ENCRYPTER_KEY=def000000cc7dc8fccb7c6aa28497907c91cf6790df0c7db92d125c4bdf9567f3bf8cad0a1911f966eff2ec01fde754ab92ad7efb3136fa8660261d9665c0095f5316354 5 | 6 | MONOLOG_DEFAULT_CHANNEL=default # Use "roadrunner" channel if you want to use RoadRunner logger 7 | MONOLOG_DEFAULT_LEVEL=DEBUG # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY 8 | 9 | # Telemetry 10 | TELEMETRY_DRIVER=otel 11 | OTEL_TRACES_EXPORTER=otlp 12 | OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf 13 | OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 14 | OTEL_PHP_TRACES_PROCESSOR=simple 15 | 16 | USERS_SERVICE_HOST=users:9001 17 | AUTH_SERVICE_HOST=users:9001 18 | PAYMENT_SERVICE_HOST=payment:9002 19 | 20 | DB_CONNECTION: pgsql 21 | DB_HOST: db 22 | DB_PORT: 5432 23 | DB_USERNAME: homestead 24 | DB_PASSWORD: secret 25 | 26 | TEMPORAL_ADDRESS=temporal:7233 27 | 28 | VAR_DUMPER_FORMAT=server 29 | VAR_DUMPER_SERVER=buggregator:9912 30 | 31 | SENTRY_DSN=http://sentry@buggregator:8000/1 32 | 33 | TOKENIZER_CACHE_TARGETS=true 34 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/shelf/Changes.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/shelf/Changes/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/.idea/shelf/Changes/favicon.ico -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker compose up --no-start; 3 | 4 | start: 5 | docker compose up --remove-orphans -d; 6 | 7 | up: build start 8 | 9 | stop: 10 | docker compose stop; 11 | 12 | down: 13 | docker compose down; 14 | 15 | restart: 16 | docker compose restart; 17 | 18 | list: 19 | docker compose ps; 20 | 21 | log-tail: 22 | docker compose logs --tail=50 -f; 23 | 24 | # ========================= 25 | 26 | install: 27 | for service in web users; do \ 28 | cd $$service; \ 29 | composer install; \ 30 | cd ..; \ 31 | done 32 | 33 | 34 | reinstall-temporal-shared: 35 | for service in web users; do \ 36 | cd $$service; \ 37 | rm -rf vendor/ms/temporal-shared; \ 38 | composer require ms/temporal-shared; \ 39 | cd ..; \ 40 | done 41 | 42 | reinstall-grpc-shared: 43 | for service in web users; do \ 44 | cd $$service; \ 45 | rm -rf vendor/ms/grpc-shared; \ 46 | composer require ms/grpc-shared; \ 47 | cd ..; \ 48 | done 49 | 50 | clear-cache: 51 | for service in web users; do \ 52 | cd $$service; \ 53 | rm -rf runtime/cache; \ 54 | cd ..; \ 55 | done 56 | 57 | compile-proto: 58 | chmod +x lib/grpc-shared/bin/console 59 | chmod +x lib/grpc-shared/bin/protoc-gen-php-grpc 60 | php lib/grpc-shared/bin/console generate; 61 | 62 | update-proto: compile-proto reinstall-grpc-shared; 63 | 64 | composer-du: 65 | for service in web users; do \ 66 | cd $$service; \ 67 | composer du; \ 68 | cd ..; \ 69 | done 70 | 71 | # ========================= 72 | 73 | bash-web: 74 | docker compose exec web /bin/sh; 75 | 76 | bash-users: 77 | docker compose exec users /bin/sh; 78 | 79 | reset-web: 80 | docker compose exec web ./rr reset; 81 | 82 | reset-users: 83 | docker compose exec users ./rr reset; 84 | 85 | reset: reset-web reset-users 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is a part of streams about developing microservices. 2 | 3 | In a video we develop microservices using PHP, Spiral Framework, RoadRunner, Docker and gRPC. 4 | 5 | Link to the [YouTube playlist](https://www.youtube.com/playlist?list=PLq5yMPNm4NaCBXMGlIeIDA5SLYRIDSK5e). 6 | 7 | ## Installation 8 | 9 | 1. Clone the repository 10 | 2. Install make and docker 11 | 3. Run `make install` to install composer dependencies 12 | 4. Run `make up` to start docker containers 13 | 14 | 15 | ## TODO: 16 | 1. Add bash scripts for db initialization 17 | -------------------------------------------------------------------------------- /lib/grpc-shared/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ -------------------------------------------------------------------------------- /lib/grpc-shared/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | isDir() && !\str_starts_with($fileInfo->getFilename(), '.')) { 17 | $protoDirs[] = $fileInfo->getPathname(); 18 | } 19 | } 20 | 21 | $application->add( 22 | new GeneratorCommand( 23 | files: new \Spiral\Files\Files(), 24 | rootDir: __DIR__ . '/../', 25 | protoFileDirs: $protoDirs, 26 | ), 27 | ); 28 | 29 | $application->run(); 30 | -------------------------------------------------------------------------------- /lib/grpc-shared/bin/protoc-gen-php-grpc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/bin/protoc-gen-php-grpc -------------------------------------------------------------------------------- /lib/grpc-shared/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ms/grpc-shared", 3 | "description": "This is shared package", 4 | "keywords": [], 5 | "homepage": "https://github.com/spiral/framework", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "butschster", 10 | "email": "butschster@gmail.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": "^8.2", 16 | "ext-grpc": "*", 17 | "grpc/grpc": "^1.42", 18 | "spiral/boot": "^3.7", 19 | "spiral/hmvc": "^3.7", 20 | "spiral/files": "^3.7", 21 | "spiral/console": "^3.7", 22 | "spiral/auth": "^3.7", 23 | "spiral/telemetry": "^3.7", 24 | "spiral-packages/cqrs": "^2.0", 25 | "spiral/roadrunner-grpc": "^3.0", 26 | "spiral/roadrunner-bridge": "^3.6" 27 | }, 28 | "require-dev": { 29 | "spiral/reactor": "^3.13", 30 | "nikic/php-parser": "^v4.19", 31 | "symfony/console": "^7.0", 32 | "spiral/roadrunner-cli": "^2.5", 33 | "doctrine/annotations": "^2.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Internal\\Shared\\gRPC\\": "src", 38 | "GRPC\\": "generated/GRPC", 39 | "Generator\\": "generator" 40 | } 41 | }, 42 | "config": { 43 | "sort-packages": true 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true 47 | } 48 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Message.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Message.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Request.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Request.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Response.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Response.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Service.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Auth/v1/Service.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Common/v1/Message.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Common/v1/Message.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Message.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Message.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Request.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Request.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Response.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Response.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Service.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Payment/v1/Service.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Message.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Message.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Request.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Request.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Response.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Response.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Service.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/lib/grpc-shared/generated/GRPC/ProtobufMetadata/Users/v1/Service.php -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/AuthServiceClient.php: -------------------------------------------------------------------------------- 1 | core->callAction(AuthServiceInterface::class, '/'.self::NAME.'/Login', [ 20 | 'in' => $in, 21 | 'ctx' => $ctx, 22 | 'responseClass' => \GRPC\Services\Auth\v1\LoginResponse::class, 23 | ]); 24 | 25 | return $response; 26 | } 27 | 28 | public function Logout(ContextInterface $ctx, LogoutRequest $in): \GRPC\Services\Common\v1\PBEmpty 29 | { 30 | [$response, $status] = $this->core->callAction(AuthServiceInterface::class, '/'.self::NAME.'/Logout', [ 31 | 'in' => $in, 32 | 'ctx' => $ctx, 33 | 'responseClass' => \GRPC\Services\Common\v1\PBEmpty::class, 34 | ]); 35 | 36 | return $response; 37 | } 38 | 39 | public function Register(ContextInterface $ctx, RegisterRequest $in): RegisterResponse 40 | { 41 | [$response, $status] = $this->core->callAction(AuthServiceInterface::class, '/'.self::NAME.'/Register', [ 42 | 'in' => $in, 43 | 'ctx' => $ctx, 44 | 'responseClass' => \GRPC\Services\Auth\v1\RegisterResponse::class, 45 | ]); 46 | 47 | return $response; 48 | } 49 | 50 | public function Me(ContextInterface $ctx, MeRequest $in): MeResponse 51 | { 52 | [$response, $status] = $this->core->callAction(AuthServiceInterface::class, '/'.self::NAME.'/Me', [ 53 | 'in' => $in, 54 | 'ctx' => $ctx, 55 | 'responseClass' => \GRPC\Services\Auth\v1\MeResponse::class, 56 | ]); 57 | 58 | return $response; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/AuthServiceInterface.php: -------------------------------------------------------------------------------- 1 | auth.v1.request.LoginRequest 11 | */ 12 | class LoginRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field string email = 1; */ 15 | protected $email = ''; 16 | 17 | /** Generated from protobuf field string password = 2; */ 18 | protected $password = ''; 19 | 20 | /** 21 | * Constructor. 22 | * 23 | * @param array $data { 24 | * Optional. Data for populating the Message object. 25 | * 26 | * @type string $email 27 | * @type string $password 28 | * } 29 | */ 30 | public function __construct($data = null) 31 | { 32 | \GRPC\ProtobufMetadata\Auth\v1\Request::initOnce(); 33 | parent::__construct($data); 34 | } 35 | 36 | /** 37 | * Generated from protobuf field string email = 1; 38 | * @return string 39 | */ 40 | public function getEmail() 41 | { 42 | return $this->email; 43 | } 44 | 45 | /** 46 | * Generated from protobuf field string email = 1; 47 | * @param string $var 48 | * @return $this 49 | */ 50 | public function setEmail($var) 51 | { 52 | GPBUtil::checkString($var, True); 53 | $this->email = $var; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Generated from protobuf field string password = 2; 60 | * @return string 61 | */ 62 | public function getPassword() 63 | { 64 | return $this->password; 65 | } 66 | 67 | /** 68 | * Generated from protobuf field string password = 2; 69 | * @param string $var 70 | * @return $this 71 | */ 72 | public function setPassword($var) 73 | { 74 | GPBUtil::checkString($var, True); 75 | $this->password = $var; 76 | 77 | return $this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/LoginResponse.php: -------------------------------------------------------------------------------- 1 | auth.v1.response.LoginResponse 11 | */ 12 | class LoginResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .auth.v1.dto.Token token = 1; */ 15 | protected $token = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Auth\v1\Token $token 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Auth\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .auth.v1.dto.Token token = 1; 34 | * @return \GRPC\Services\Auth\v1\Token|null 35 | */ 36 | public function getToken() 37 | { 38 | return isset($this->token) ? $this->token : null; 39 | } 40 | 41 | public function hasToken() 42 | { 43 | return isset($this->token); 44 | } 45 | 46 | public function clearToken() 47 | { 48 | unset($this->token); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .auth.v1.dto.Token token = 1; 53 | * @param \GRPC\Services\Auth\v1\Token $var 54 | * @return $this 55 | */ 56 | public function setToken($var) 57 | { 58 | GPBUtil::checkMessage($var, Token::class); 59 | $this->token = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/LogoutRequest.php: -------------------------------------------------------------------------------- 1 | auth.v1.request.LogoutRequest 11 | */ 12 | class LogoutRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field string token = 1; */ 15 | protected $token = ''; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type string $token 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Auth\v1\Request::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field string token = 1; 34 | * @return string 35 | */ 36 | public function getToken() 37 | { 38 | return $this->token; 39 | } 40 | 41 | /** 42 | * Generated from protobuf field string token = 1; 43 | * @param string $var 44 | * @return $this 45 | */ 46 | public function setToken($var) 47 | { 48 | GPBUtil::checkString($var, True); 49 | $this->token = $var; 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/MeRequest.php: -------------------------------------------------------------------------------- 1 | auth.v1.request.MeRequest 11 | */ 12 | class MeRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field string token = 1; */ 15 | protected $token = ''; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type string $token 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Auth\v1\Request::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field string token = 1; 34 | * @return string 35 | */ 36 | public function getToken() 37 | { 38 | return $this->token; 39 | } 40 | 41 | /** 42 | * Generated from protobuf field string token = 1; 43 | * @param string $var 44 | * @return $this 45 | */ 46 | public function setToken($var) 47 | { 48 | GPBUtil::checkString($var, True); 49 | $this->token = $var; 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/MeResponse.php: -------------------------------------------------------------------------------- 1 | auth.v1.response.MeResponse 11 | */ 12 | class MeResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .users.v1.dto.User user = 1; */ 15 | protected $user = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Users\v1\User $user 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Auth\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .users.v1.dto.User user = 1; 34 | * @return \GRPC\Services\Users\v1\User|null 35 | */ 36 | public function getUser() 37 | { 38 | return isset($this->user) ? $this->user : null; 39 | } 40 | 41 | public function hasUser() 42 | { 43 | return isset($this->user); 44 | } 45 | 46 | public function clearUser() 47 | { 48 | unset($this->user); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .users.v1.dto.User user = 1; 53 | * @param \GRPC\Services\Users\v1\User $var 54 | * @return $this 55 | */ 56 | public function setUser($var) 57 | { 58 | GPBUtil::checkMessage($var, \GRPC\Services\Users\v1\User::class); 59 | $this->user = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Auth/v1/RegisterResponse.php: -------------------------------------------------------------------------------- 1 | auth.v1.response.RegisterResponse 11 | */ 12 | class RegisterResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .auth.v1.dto.Token token = 1; */ 15 | protected $token = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Auth\v1\Token $token 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Auth\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .auth.v1.dto.Token token = 1; 34 | * @return \GRPC\Services\Auth\v1\Token|null 35 | */ 36 | public function getToken() 37 | { 38 | return isset($this->token) ? $this->token : null; 39 | } 40 | 41 | public function hasToken() 42 | { 43 | return isset($this->token); 44 | } 45 | 46 | public function clearToken() 47 | { 48 | unset($this->token); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .auth.v1.dto.Token token = 1; 53 | * @param \GRPC\Services\Auth\v1\Token $var 54 | * @return $this 55 | */ 56 | public function setToken($var) 57 | { 58 | GPBUtil::checkMessage($var, Token::class); 59 | $this->token = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Common/v1/PBEmpty.php: -------------------------------------------------------------------------------- 1 | common.v1.dto.Empty 11 | */ 12 | class PBEmpty extends \Google\Protobuf\Internal\Message 13 | { 14 | /** 15 | * Constructor. 16 | * 17 | * @param array $data { 18 | * Optional. Data for populating the Message object. 19 | * 20 | * } 21 | */ 22 | public function __construct($data = null) 23 | { 24 | \GRPC\ProtobufMetadata\Common\v1\Message::initOnce(); 25 | parent::__construct($data); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Payment/v1/ChargeRequest.php: -------------------------------------------------------------------------------- 1 | payment.v1.request.ChargeRequest 11 | */ 12 | class ChargeRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .payment.v1.dto.Payment payment = 1; */ 15 | protected $payment = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Payment\v1\Payment $payment 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Payment\v1\Request::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .payment.v1.dto.Payment payment = 1; 34 | * @return \GRPC\Services\Payment\v1\Payment|null 35 | */ 36 | public function getPayment() 37 | { 38 | return isset($this->payment) ? $this->payment : null; 39 | } 40 | 41 | public function hasPayment() 42 | { 43 | return isset($this->payment); 44 | } 45 | 46 | public function clearPayment() 47 | { 48 | unset($this->payment); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .payment.v1.dto.Payment payment = 1; 53 | * @param \GRPC\Services\Payment\v1\Payment $var 54 | * @return $this 55 | */ 56 | public function setPayment($var) 57 | { 58 | GPBUtil::checkMessage($var, Payment::class); 59 | $this->payment = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Payment/v1/ChargeResponse.php: -------------------------------------------------------------------------------- 1 | payment.v1.response.ChargeResponse 11 | */ 12 | class ChargeResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .payment.v1.dto.Receipt receipt = 1; */ 15 | protected $receipt = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Payment\v1\Receipt $receipt 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Payment\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .payment.v1.dto.Receipt receipt = 1; 34 | * @return \GRPC\Services\Payment\v1\Receipt|null 35 | */ 36 | public function getReceipt() 37 | { 38 | return isset($this->receipt) ? $this->receipt : null; 39 | } 40 | 41 | public function hasReceipt() 42 | { 43 | return isset($this->receipt); 44 | } 45 | 46 | public function clearReceipt() 47 | { 48 | unset($this->receipt); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .payment.v1.dto.Receipt receipt = 1; 53 | * @param \GRPC\Services\Payment\v1\Receipt $var 54 | * @return $this 55 | */ 56 | public function setReceipt($var) 57 | { 58 | GPBUtil::checkMessage($var, Receipt::class); 59 | $this->receipt = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Payment/v1/Money.php: -------------------------------------------------------------------------------- 1 | payment.v1.dto.Money 11 | */ 12 | class Money extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field string currency_code = 1; */ 15 | protected $currency_code = ''; 16 | 17 | /** Generated from protobuf field int64 amount = 2; */ 18 | protected $amount = 0; 19 | 20 | /** 21 | * Constructor. 22 | * 23 | * @param array $data { 24 | * Optional. Data for populating the Message object. 25 | * 26 | * @type string $currency_code 27 | * @type int|string $amount 28 | * } 29 | */ 30 | public function __construct($data = null) 31 | { 32 | \GRPC\ProtobufMetadata\Payment\v1\Message::initOnce(); 33 | parent::__construct($data); 34 | } 35 | 36 | /** 37 | * Generated from protobuf field string currency_code = 1; 38 | * @return string 39 | */ 40 | public function getCurrencyCode() 41 | { 42 | return $this->currency_code; 43 | } 44 | 45 | /** 46 | * Generated from protobuf field string currency_code = 1; 47 | * @param string $var 48 | * @return $this 49 | */ 50 | public function setCurrencyCode($var) 51 | { 52 | GPBUtil::checkString($var, True); 53 | $this->currency_code = $var; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Generated from protobuf field int64 amount = 2; 60 | * @return int|string 61 | */ 62 | public function getAmount() 63 | { 64 | return $this->amount; 65 | } 66 | 67 | /** 68 | * Generated from protobuf field int64 amount = 2; 69 | * @param int|string $var 70 | * @return $this 71 | */ 72 | public function setAmount($var) 73 | { 74 | GPBUtil::checkInt64($var); 75 | $this->amount = $var; 76 | 77 | return $this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Payment/v1/PaymentServiceClient.php: -------------------------------------------------------------------------------- 1 | core->callAction(PaymentServiceInterface::class, '/'.self::NAME.'/Charge', [ 20 | 'in' => $in, 21 | 'ctx' => $ctx, 22 | 'responseClass' => \GRPC\Services\Payment\v1\ChargeResponse::class, 23 | ]); 24 | 25 | return $response; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Payment/v1/PaymentServiceInterface.php: -------------------------------------------------------------------------------- 1 | users.v1.request.CreateRequest 11 | */ 12 | class CreateRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .users.v1.request.CreateRequest.User user = 1; */ 15 | protected $user = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Users\v1\CreateRequest\User $user 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Users\v1\Request::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .users.v1.request.CreateRequest.User user = 1; 34 | * @return \GRPC\Services\Users\v1\CreateRequest\User|null 35 | */ 36 | public function getUser() 37 | { 38 | return isset($this->user) ? $this->user : null; 39 | } 40 | 41 | public function hasUser() 42 | { 43 | return isset($this->user); 44 | } 45 | 46 | public function clearUser() 47 | { 48 | unset($this->user); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .users.v1.request.CreateRequest.User user = 1; 53 | * @param \GRPC\Services\Users\v1\CreateRequest\User $var 54 | * @return $this 55 | */ 56 | public function setUser($var) 57 | { 58 | GPBUtil::checkMessage($var, CreateRequest\User::class); 59 | $this->user = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/CreateRequest_User.php: -------------------------------------------------------------------------------- 1 | users.v1.response.CreateResponse 11 | */ 12 | class CreateResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .users.v1.dto.User user = 1; */ 15 | protected $user = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Users\v1\User $user 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Users\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .users.v1.dto.User user = 1; 34 | * @return \GRPC\Services\Users\v1\User|null 35 | */ 36 | public function getUser() 37 | { 38 | return isset($this->user) ? $this->user : null; 39 | } 40 | 41 | public function hasUser() 42 | { 43 | return isset($this->user); 44 | } 45 | 46 | public function clearUser() 47 | { 48 | unset($this->user); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .users.v1.dto.User user = 1; 53 | * @param \GRPC\Services\Users\v1\User $var 54 | * @return $this 55 | */ 56 | public function setUser($var) 57 | { 58 | GPBUtil::checkMessage($var, User::class); 59 | $this->user = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/GetRequest.php: -------------------------------------------------------------------------------- 1 | users.v1.request.GetRequest 11 | */ 12 | class GetRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field string uuid = 1; */ 15 | protected $uuid = ''; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type string $uuid 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Users\v1\Request::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field string uuid = 1; 34 | * @return string 35 | */ 36 | public function getUuid() 37 | { 38 | return $this->uuid; 39 | } 40 | 41 | /** 42 | * Generated from protobuf field string uuid = 1; 43 | * @param string $var 44 | * @return $this 45 | */ 46 | public function setUuid($var) 47 | { 48 | GPBUtil::checkString($var, True); 49 | $this->uuid = $var; 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/GetResponse.php: -------------------------------------------------------------------------------- 1 | users.v1.response.GetResponse 11 | */ 12 | class GetResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .users.v1.dto.User user = 1; */ 15 | protected $user = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Users\v1\User $user 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Users\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .users.v1.dto.User user = 1; 34 | * @return \GRPC\Services\Users\v1\User|null 35 | */ 36 | public function getUser() 37 | { 38 | return isset($this->user) ? $this->user : null; 39 | } 40 | 41 | public function hasUser() 42 | { 43 | return isset($this->user); 44 | } 45 | 46 | public function clearUser() 47 | { 48 | unset($this->user); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .users.v1.dto.User user = 1; 53 | * @param \GRPC\Services\Users\v1\User $var 54 | * @return $this 55 | */ 56 | public function setUser($var) 57 | { 58 | GPBUtil::checkMessage($var, User::class); 59 | $this->user = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/UpdateRequest.php: -------------------------------------------------------------------------------- 1 | users.v1.request.UpdateRequest 11 | */ 12 | class UpdateRequest extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .users.v1.request.UpdateRequest.User user = 1; */ 15 | protected $user = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Users\v1\UpdateRequest\User $user 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Users\v1\Request::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .users.v1.request.UpdateRequest.User user = 1; 34 | * @return \GRPC\Services\Users\v1\UpdateRequest\User|null 35 | */ 36 | public function getUser() 37 | { 38 | return isset($this->user) ? $this->user : null; 39 | } 40 | 41 | public function hasUser() 42 | { 43 | return isset($this->user); 44 | } 45 | 46 | public function clearUser() 47 | { 48 | unset($this->user); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .users.v1.request.UpdateRequest.User user = 1; 53 | * @param \GRPC\Services\Users\v1\UpdateRequest\User $var 54 | * @return $this 55 | */ 56 | public function setUser($var) 57 | { 58 | GPBUtil::checkMessage($var, UpdateRequest\User::class); 59 | $this->user = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/UpdateRequest_User.php: -------------------------------------------------------------------------------- 1 | users.v1.response.UpdateResponse 11 | */ 12 | class UpdateResponse extends \Google\Protobuf\Internal\Message 13 | { 14 | /** Generated from protobuf field .users.v1.dto.User user = 1; */ 15 | protected $user = null; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param array $data { 21 | * Optional. Data for populating the Message object. 22 | * 23 | * @type \GRPC\Services\Users\v1\User $user 24 | * } 25 | */ 26 | public function __construct($data = null) 27 | { 28 | \GRPC\ProtobufMetadata\Users\v1\Response::initOnce(); 29 | parent::__construct($data); 30 | } 31 | 32 | /** 33 | * Generated from protobuf field .users.v1.dto.User user = 1; 34 | * @return \GRPC\Services\Users\v1\User|null 35 | */ 36 | public function getUser() 37 | { 38 | return isset($this->user) ? $this->user : null; 39 | } 40 | 41 | public function hasUser() 42 | { 43 | return isset($this->user); 44 | } 45 | 46 | public function clearUser() 47 | { 48 | unset($this->user); 49 | } 50 | 51 | /** 52 | * Generated from protobuf field .users.v1.dto.User user = 1; 53 | * @param \GRPC\Services\Users\v1\User $var 54 | * @return $this 55 | */ 56 | public function setUser($var) 57 | { 58 | GPBUtil::checkMessage($var, User::class); 59 | $this->user = $var; 60 | 61 | return $this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/UsersServiceClient.php: -------------------------------------------------------------------------------- 1 | core->callAction(UsersServiceInterface::class, '/'.self::NAME.'/Get', [ 20 | 'in' => $in, 21 | 'ctx' => $ctx, 22 | 'responseClass' => \GRPC\Services\Users\v1\GetResponse::class, 23 | ]); 24 | 25 | return $response; 26 | } 27 | 28 | public function Create(ContextInterface $ctx, CreateRequest $in): CreateResponse 29 | { 30 | [$response, $status] = $this->core->callAction(UsersServiceInterface::class, '/'.self::NAME.'/Create', [ 31 | 'in' => $in, 32 | 'ctx' => $ctx, 33 | 'responseClass' => \GRPC\Services\Users\v1\CreateResponse::class, 34 | ]); 35 | 36 | return $response; 37 | } 38 | 39 | public function Update(ContextInterface $ctx, UpdateRequest $in): UpdateResponse 40 | { 41 | [$response, $status] = $this->core->callAction(UsersServiceInterface::class, '/'.self::NAME.'/Update', [ 42 | 'in' => $in, 43 | 'ctx' => $ctx, 44 | 'responseClass' => \GRPC\Services\Users\v1\UpdateResponse::class, 45 | ]); 46 | 47 | return $response; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/grpc-shared/generated/GRPC/Services/Users/v1/UsersServiceInterface.php: -------------------------------------------------------------------------------- 1 | $class 15 | * @param PropertyType[] $properties 16 | */ 17 | public function __construct( 18 | public string $class, 19 | public array $properties, 20 | public array $attributes, 21 | ) { 22 | } 23 | 24 | public function isGuarded(): bool 25 | { 26 | foreach ($this->attributes as $attribute) { 27 | if ($attribute instanceof Annotation\Guarded) { 28 | return true; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/Generators/Message/MessageClassParser.php: -------------------------------------------------------------------------------- 1 | $class 21 | * @throws \ReflectionException 22 | */ 23 | public function parse(string $class): MessageClass 24 | { 25 | $reflection = new \ReflectionClass($class); 26 | 27 | if (!$reflection->isSubclassOf(Message::class)) { 28 | throw new \InvalidArgumentException(\sprintf('Class %s is not a subclass of %s', $class, Message::class)); 29 | } 30 | 31 | new $class; 32 | $pool = DescriptorPool::getGeneratedPool(); 33 | $descriptor = $pool->getDescriptorByClassName($class); 34 | 35 | $method = $reflection->getMethod('__construct'); 36 | 37 | $parser = new MessageCommentsParser(new TypeFactory()); 38 | $properties = $parser->parse($descriptor, $method); 39 | 40 | return new MessageClass( 41 | $class, 42 | $properties, 43 | $this->annotationsParser->parseFromClass($reflection), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/PHP/ClassDeclaration.php: -------------------------------------------------------------------------------- 1 | class->getName(); 26 | } 27 | 28 | public function markAsFinal(): void 29 | { 30 | $this->class->setFinal(true); 31 | } 32 | 33 | public function markAsReadonly(): void 34 | { 35 | $this->class->getElement()->setReadonly(true); 36 | } 37 | 38 | public function getNameWithNamespace(): string 39 | { 40 | return $this->getNamespace() . '\\' . $this->getName(); 41 | } 42 | 43 | public function getNamespace(): string 44 | { 45 | return $this->namespace->getName(); 46 | } 47 | 48 | public function isClassNameEndsWith(string $suffix): bool 49 | { 50 | return \str_ends_with($this->getName(), $suffix); 51 | } 52 | 53 | public function persist(): void 54 | { 55 | (new Writer($this->files))->write($this->filePath, $this->file); 56 | } 57 | 58 | public function getReflection(): \ReflectionClass 59 | { 60 | return new \ReflectionClass($this->getNameWithNamespace()); 61 | } 62 | 63 | public function addImplement(string $interface): void 64 | { 65 | $this->class->addImplement($interface); 66 | } 67 | 68 | public function __toString() 69 | { 70 | return $this->filePath; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/PHP/ClassTransformer.php: -------------------------------------------------------------------------------- 1 | class); 17 | 18 | return \implode('\\', \array_slice($segments, 0, -1)); 19 | } 20 | 21 | public function getShortName(): string 22 | { 23 | $segments = \explode('\\', $this->class); 24 | 25 | return \array_pop($segments); 26 | } 27 | 28 | public function cleanNamespace(?string $prefix = null): self 29 | { 30 | $namespace = $this->getNamespace(); 31 | 32 | if (\str_starts_with($namespace, 'Internal\\Shared\\gRPC\\Services\\')) { 33 | $namespace = \str_replace('Internal\\Shared\\gRPC\\Services\\', '', $namespace); 34 | 35 | if ($prefix) { 36 | $prefix = \explode('\\', $prefix); 37 | $namespace = \explode('\\', $namespace); 38 | $namespace = \implode('\\', \array_filter([...$prefix, ...$namespace])); 39 | } 40 | } 41 | 42 | return new self($namespace . '\\' . $this->getShortName()); 43 | } 44 | 45 | public function getDirectoryPath(): string 46 | { 47 | return \implode('/', \explode('\\', $this->getNamespace())); 48 | } 49 | 50 | public function getFilePath(?string $suffix = null): string 51 | { 52 | $filename = $this->getShortName(); 53 | if ($suffix) { 54 | $filename .= \ucfirst($suffix); 55 | } 56 | 57 | return $this->getDirectoryPath() . '/' . $filename . '.php'; 58 | } 59 | 60 | public function __toString() 61 | { 62 | return $this->class; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/PHP/Property/BuiltInType.php: -------------------------------------------------------------------------------- 1 | transformer = new ClassTransformer($type); 20 | } 21 | 22 | public function getShortName(): string 23 | { 24 | return $this->transformer->getShortName(); 25 | } 26 | 27 | public function getName(): string 28 | { 29 | return $this->transformer->class; 30 | } 31 | 32 | public function getNamespace(): string 33 | { 34 | return $this->transformer->getNamespace(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/PHP/Property/DateTime.php: -------------------------------------------------------------------------------- 1 | iterableType = $type; 14 | parent::__construct('array', $type->type . '[]'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/PHP/Property/Timestamp.php: -------------------------------------------------------------------------------- 1 | docType = $doctype ?? $type; 16 | } 17 | 18 | public function isEqual(string $type): bool 19 | { 20 | return $this->type === $type; 21 | } 22 | 23 | public function __toString(): string 24 | { 25 | return $this->type; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/grpc-shared/generator/ProtocCommandBuilder.php: -------------------------------------------------------------------------------- 1 | getProtoFiles($protoDir); 23 | if ($files === []) { 24 | return ''; 25 | } 26 | 27 | return \sprintf( 28 | 'protoc %s --php_out=%s --php-grpc_out=%s%s %s 2>&1', 29 | $this->protocBinaryPath ? '--plugin=' . $this->protocBinaryPath : '', 30 | \escapeshellarg($tmpDir), 31 | \escapeshellarg($tmpDir), 32 | $this->buildDirs($protoDir), 33 | \implode(' ', \array_map('escapeshellarg', $files)), 34 | ); 35 | } 36 | 37 | /** 38 | * Include all proto files from the directory. 39 | */ 40 | private function getProtoFiles(string $protoDir): array 41 | { 42 | return \array_filter( 43 | $this->files->getFiles($protoDir), 44 | static fn(string $file) => \str_ends_with($file, '.proto') && !\str_contains($file, 'google'), 45 | ); 46 | } 47 | 48 | private function buildDirs(string $protoDir): string 49 | { 50 | $dirs = \array_filter([ 51 | $this->basePath, 52 | $protoDir, 53 | ]); 54 | 55 | if ($dirs === []) { 56 | return ''; 57 | } 58 | 59 | return ' -I=' . \implode(' -I=', \array_map('escapeshellarg', $dirs)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/grpc-shared/src/Attribute/Guarded.php: -------------------------------------------------------------------------------- 1 | [] 19 | * } 20 | */ 21 | protected array $config = ['services' => [], 'interceptors' => []]; 22 | 23 | public function getDefaultCredentials(): ChannelCredentials|null 24 | { 25 | return ChannelCredentials::createInsecure(); 26 | } 27 | 28 | public function getInterceptors(): array 29 | { 30 | return $this->config['interceptors']; 31 | } 32 | 33 | /** 34 | * Get service definition. 35 | * @return array{host: string, credentials?: mixed} 36 | */ 37 | public function getService(string $name): array 38 | { 39 | return $this->config['services'][$name] ?? [ 40 | 'host' => 'localhost', 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/grpc-shared/src/Interceptors/Incoming/ContextInterceptor.php: -------------------------------------------------------------------------------- 1 | container->runScope([ 24 | ContextInterface::class => $parameters['ctx'], 25 | ], static fn() => $core->callAction($controller, $action, $parameters)); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/grpc-shared/src/Interceptors/Incoming/ExceptionHandlerInterceptor.php: -------------------------------------------------------------------------------- 1 | callAction($controller, $action, $parameters); 23 | 24 | $statusCode = (int) ($response[1]?->code ?? StatusCode::UNKNOWN); 25 | 26 | if ($statusCode === StatusCode::OK) { 27 | return $response; 28 | } 29 | 30 | Message::initOnce(); 31 | 32 | $status = new Status(); 33 | $status->mergeFromString($response[1]->metadata['grpc-status-details-bin'][0]); 34 | 35 | // TODO: use exception DTO 36 | match ($response[1]->details) { 37 | 'users.user_not_found' => throw new NotFoundException(), 38 | default => throw new \RuntimeException($status->getMessage()), 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/grpc-shared/src/Interceptors/Incoming/OpenTelemetryInterceptor.php: -------------------------------------------------------------------------------- 1 | getTelemetry(); 25 | 26 | return $this->tracerFactory 27 | ->make($telemetryContext) 28 | ->trace( 29 | name: \sprintf('Incoming GRPC %s::%s', $controller, $action), 30 | callback: static fn() => $core->callAction($controller, $action, $parameters), 31 | attributes: [ 32 | 'controller' => $controller, 33 | 'action' => $action, 34 | ], 35 | scoped: true, 36 | traceKind: TraceKind::SERVER, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/grpc-shared/src/Interceptors/Outgoing/SendTraceContextInterceptor.php: -------------------------------------------------------------------------------- 1 | withTelemetryContext($this->tracer->getContext()); 26 | 27 | return $this->tracer->trace( 28 | name: \sprintf('GRPC request %s', $action), 29 | callback: static fn() => $core->callAction($controller, $action, $parameters), 30 | attributes: [ 31 | 'controller' => $controller, 32 | 'action' => $action, 33 | ], 34 | traceKind: TraceKind::CLIENT, 35 | ); 36 | } 37 | } -------------------------------------------------------------------------------- /lib/temporal-shared/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ -------------------------------------------------------------------------------- /lib/temporal-shared/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ms/temporal-shared", 3 | "description": "This is shared package", 4 | "keywords": [], 5 | "homepage": "https://github.com/spiral/framework", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "butschster", 10 | "email": "butschster@gmail.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": "^8.2", 16 | "ext-grpc": "*", 17 | "spiral/boot": "^3.13", 18 | "ramsey/uuid": "^4.7", 19 | "temporal/sdk": "^2.10", 20 | "temporal-php/support": "^1.0" 21 | }, 22 | "require-dev": { 23 | "spiral/roadrunner-cli": "^2.5" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Internal\\Shared\\Temporal\\": "src" 28 | } 29 | }, 30 | "config": { 31 | "sort-packages": true 32 | }, 33 | "minimum-stability": "dev", 34 | "prefer-stable": true 35 | } 36 | -------------------------------------------------------------------------------- /lib/temporal-shared/src/Activity/NotificationsActivity.php: -------------------------------------------------------------------------------- 1 | addDirectory($dirs->get('root') . 'vendor/ms/temporal-shared/src'); 18 | } 19 | } -------------------------------------------------------------------------------- /lib/temporal-shared/src/OptionsTrait.php: -------------------------------------------------------------------------------- 1 | withTaskQueue($taskQueue) 28 | ->withStartToCloseTimeout($timeout ?? self::TIMEOUT) 29 | ->withRetryOptions( 30 | RetryOptions::new() 31 | ->withMaximumAttempts($retryAttempts ?? self::RETRY_ATTEMPTS) 32 | ->withBackoffCoefficient(2.5), 33 | ); 34 | } 35 | 36 | /** 37 | * Must be used for each workflow activity 38 | * 39 | * @param string $taskQueue used task queue from TaskQueue 40 | * 41 | * @return ChildWorkflowOptions 42 | */ 43 | private function workflowOptions( 44 | string $taskQueue, 45 | ): ChildWorkflowOptions { 46 | return ChildWorkflowOptions::new()->withTaskQueue($taskQueue); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/temporal-shared/src/TaskQueue.php: -------------------------------------------------------------------------------- 1 | getPrevious()) { 20 | if (\method_exists($e, 'getType') && \in_array($e->getType(), $exception, true)) { 21 | return true; 22 | } 23 | } 24 | 25 | return false; 26 | } 27 | 28 | public static function getApplicationFailure(\Throwable $e): ?ApplicationFailure 29 | { 30 | if (!$e instanceof WorkflowException) { 31 | return null; 32 | } 33 | 34 | while ($e = $e->getPrevious()) { 35 | if ($e instanceof ApplicationFailure) { 36 | return $e; 37 | } 38 | } 39 | 40 | return new ApplicationFailure('Unknown error'); 41 | } 42 | 43 | public static function convertToRealException(\Throwable $e): \Throwable 44 | { 45 | $failure = self::getApplicationFailure($e); 46 | 47 | if ($failure === null) { 48 | return $e; 49 | } 50 | 51 | $type = $failure->getType(); 52 | 53 | return new $type($failure->getOriginalMessage(), $failure->getCode(), $e); 54 | } 55 | } -------------------------------------------------------------------------------- /lib/temporal-shared/src/Workflow/KYCWorkflow.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | #[WorkflowMethod] 24 | public function process(UuidInterface $userUuid, string $verificationId); 25 | 26 | #[SignalMethod] 27 | public function updateStatus(bool $status): void; 28 | 29 | #[QueryMethod] 30 | public function currentStatus(): bool; 31 | } -------------------------------------------------------------------------------- /lib/temporal-shared/src/Workflow/RegisterUserWorkflow.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | #[UpdateMethod(name: 'emailVerified')] 31 | public function emailVerified(); 32 | 33 | /** 34 | * @return VirtualPromise 35 | */ 36 | #[UpdateMethod(name: 'kycVerified')] 37 | public function kycVerified(bool $status); 38 | 39 | /** 40 | * @return VirtualPromise 41 | */ 42 | #[UpdateMethod(name: 'subscriptionPaid')] 43 | public function subscriptionPaid(string $transactionId); 44 | 45 | #[UpdateValidatorMethod(forUpdate: 'subscriptionPaid')] 46 | public function validateSubscriptionTransaction(string $transactionId): void; 47 | } -------------------------------------------------------------------------------- /lib/temporal-shared/src/Workflow/SubscriptionWorkflow.php: -------------------------------------------------------------------------------- 1 | __DIR__], 22 | )->run(); 23 | 24 | if ($app === null) { 25 | exit(255); 26 | } 27 | 28 | $code = (int)$app->serve(); 29 | exit($code); 30 | -------------------------------------------------------------------------------- /notifications/app/config/database.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'default' => null, 10 | 'drivers' => [ 11 | // 'runtime' => 'stdout' 12 | ], 13 | ], 14 | 15 | 'default' => 'default', 16 | 17 | 'databases' => [ 18 | 'default' => [ 19 | 'driver' => env('DB_CONNECTION', 'pgsql'), 20 | ], 21 | ], 22 | 23 | 'drivers' => [ 24 | 'pgsql' => new Config\PostgresDriverConfig( 25 | connection: new Config\Postgres\TcpConnectionConfig( 26 | database: env('DB_DATABASE', 'spiral'), 27 | host: env('DB_HOST', '127.0.0.1'), 28 | port: (int) env('DB_PORT', 5432), 29 | user: env('DB_USERNAME', 'postgres'), 30 | password: env('DB_PASSWORD', ''), 31 | ), 32 | schema: 'public', 33 | queryCache: true, 34 | ), 35 | ], 36 | ]; 37 | -------------------------------------------------------------------------------- /notifications/app/config/migration.php: -------------------------------------------------------------------------------- 1 | directory('app') . 'migrations/', 9 | 10 | 'table' => 'migrations', 11 | 12 | 'strategy' => MultipleFilesStrategy::class, 13 | 14 | 'safe' => env('APP_ENV') === 'local', 15 | ]; 16 | -------------------------------------------------------------------------------- /notifications/app/config/scaffolder.php: -------------------------------------------------------------------------------- 1 | 'App', 15 | 16 | 'declarations' => [ 17 | Declaration\BootloaderDeclaration::TYPE => [ 18 | 'namespace' => 'Application\\Bootloader', 19 | ], 20 | Declaration\ConfigDeclaration::TYPE => [ 21 | 'namespace' => 'Application\\Config', 22 | ], 23 | Declaration\ControllerDeclaration::TYPE => [ 24 | 'namespace' => 'Endpoint\\Web', 25 | ], 26 | Declaration\FilterDeclaration::TYPE => [ 27 | 'namespace' => 'Endpoint\\Web\\Filter', 28 | ], 29 | Declaration\MiddlewareDeclaration::TYPE => [ 30 | 'namespace' => 'Endpoint\\Web\\Middleware', 31 | ], 32 | Declaration\CommandDeclaration::TYPE => [ 33 | 'namespace' => 'Endpoint\\Console', 34 | ], 35 | Declaration\JobHandlerDeclaration::TYPE => [ 36 | 'namespace' => 'Endpoint\\Job', 37 | ], 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /notifications/app/config/sentry.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | tests/Unit 18 | 19 | 20 | tests/Feature 21 | 22 | 23 | 24 | 25 | app/src 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /notifications/psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /notifications/tests/App/TestKernel.php: -------------------------------------------------------------------------------- 1 | beforeBooting(static function (ConfiguratorInterface $config): void { 20 | if (!$config->exists('session')) { 21 | return; 22 | } 23 | 24 | $config->modify('session', new Set('handler', null)); 25 | }); 26 | 27 | parent::setUp(); 28 | 29 | $container = $this->getContainer(); 30 | 31 | if ($container->has(TranslatorInterface::class)) { 32 | $container->get(TranslatorInterface::class)->setLocale('en'); 33 | } 34 | } 35 | 36 | public function createAppInstance(Container $container = new Container()): TestableKernelInterface 37 | { 38 | return TestKernel::create( 39 | directories: $this->defineDirectories( 40 | $this->rootDirectory(), 41 | ), 42 | container: $container, 43 | ); 44 | } 45 | 46 | protected function tearDown(): void 47 | { 48 | // Uncomment this line if you want to clean up runtime directory. 49 | // $this->cleanUpRuntimeDirectory(); 50 | } 51 | 52 | public function rootDirectory(): string 53 | { 54 | return __DIR__ . '/..'; 55 | } 56 | 57 | public function defineDirectories(string $root): array 58 | { 59 | return [ 60 | 'root' => $root, 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /notifications/tests/Unit/DemoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($expected); 17 | $this->assertFalse($actual); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /proto/auth/v1/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package auth.v1.dto; 4 | 5 | option php_namespace = "GRPC\\Services\\Auth\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Auth\\v1"; 7 | 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | message Token { 11 | string token = 1; 12 | string type = 2; // auth, refresh, 2fa, password_reset 13 | google.protobuf.Timestamp expires_at = 3; 14 | } -------------------------------------------------------------------------------- /proto/auth/v1/request.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package auth.v1.request; 4 | 5 | option php_namespace = "GRPC\\Services\\Auth\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Auth\\v1"; 7 | 8 | message LoginRequest { 9 | string email = 1; 10 | string password = 2; 11 | } 12 | 13 | message MeRequest { 14 | string token = 1; 15 | } 16 | 17 | message LogoutRequest { 18 | string token = 1; 19 | } 20 | 21 | message RegisterRequest { 22 | string email = 1; 23 | string name = 2; 24 | string password = 3; 25 | } -------------------------------------------------------------------------------- /proto/auth/v1/response.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package auth.v1.response; 4 | 5 | option php_namespace = "GRPC\\Services\\Auth\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Auth\\v1"; 7 | 8 | import "auth/v1/message.proto"; 9 | import "users/v1/message.proto"; 10 | 11 | message LoginResponse { 12 | auth.v1.dto.Token token = 1; 13 | } 14 | 15 | message RegisterResponse { 16 | auth.v1.dto.Token token = 1; 17 | } 18 | 19 | message MeResponse { 20 | users.v1.dto.User user = 1; 21 | } -------------------------------------------------------------------------------- /proto/auth/v1/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package auth.v1; 4 | 5 | option php_namespace = "GRPC\\Services\\Auth\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Auth\\v1"; 7 | 8 | import "auth/v1/request.proto"; 9 | import "auth/v1/response.proto"; 10 | import "common/v1/message.proto"; 11 | 12 | service AuthService { 13 | rpc Login (auth.v1.request.LoginRequest) returns (auth.v1.response.LoginResponse) { 14 | } 15 | 16 | rpc Logout (auth.v1.request.LogoutRequest) returns (common.v1.dto.Empty) { 17 | } 18 | 19 | rpc Register (auth.v1.request.RegisterRequest) returns (auth.v1.response.RegisterResponse) { 20 | } 21 | 22 | rpc Me (auth.v1.request.MeRequest) returns (auth.v1.response.MeResponse) { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /proto/common/v1/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package common.v1.dto; 4 | 5 | option php_namespace = "GRPC\\Services\\Common\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Common\\v1"; 7 | 8 | message Exception { 9 | string message = 1; 10 | string code = 2; 11 | string class = 3; 12 | } 13 | 14 | message Empty {} -------------------------------------------------------------------------------- /proto/payment/v1/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package payment.v1.dto; 4 | 5 | option php_namespace = "GRPC\\Services\\Payment\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Payment\\v1"; 7 | 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | message Money { 11 | string currency_code = 1; 12 | int64 amount = 2; 13 | } 14 | 15 | message Payment { 16 | string description = 1; 17 | string email = 2; 18 | Money amount = 3; 19 | string payment_method = 4; 20 | google.protobuf.Timestamp created_at = 5; 21 | } 22 | 23 | message Receipt { 24 | string id = 1; 25 | string transaction_id = 2; 26 | Money amount = 3; 27 | Money tax = 4; 28 | google.protobuf.Timestamp paid_at = 5; 29 | } -------------------------------------------------------------------------------- /proto/payment/v1/request.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package payment.v1.request; 4 | 5 | option php_namespace = "GRPC\\Services\\Payment\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Payment\\v1"; 7 | 8 | import "payment/v1/message.proto"; 9 | 10 | message ChargeRequest { 11 | payment.v1.dto.Payment payment = 1; 12 | } -------------------------------------------------------------------------------- /proto/payment/v1/response.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package payment.v1.response; 4 | 5 | option php_namespace = "GRPC\\Services\\Payment\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Payment\\v1"; 7 | 8 | import "payment/v1/message.proto"; 9 | 10 | message ChargeResponse { 11 | payment.v1.dto.Receipt receipt = 1; 12 | } -------------------------------------------------------------------------------- /proto/payment/v1/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package payment.v1; 4 | 5 | option php_namespace = "GRPC\\Services\\Payment\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Payment\\v1"; 7 | 8 | import "payment/v1/request.proto"; 9 | import "payment/v1/response.proto"; 10 | 11 | service PaymentService { 12 | rpc Charge(payment.v1.request.ChargeRequest) returns (payment.v1.response.ChargeResponse) { 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /proto/users/v1/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package users.v1.dto; 4 | 5 | option php_namespace = "GRPC\\Services\\Users\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Users\\v1"; 7 | 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | message User { 11 | string uuid = 1; 12 | string name = 2; 13 | string email = 3; 14 | google.protobuf.Timestamp created_at = 4; 15 | google.protobuf.Timestamp updated_at = 5; 16 | } -------------------------------------------------------------------------------- /proto/users/v1/request.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package users.v1.request; 4 | 5 | option php_namespace = "GRPC\\Services\\Users\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Users\\v1"; 7 | 8 | message GetRequest { 9 | string uuid = 1; 10 | } 11 | 12 | message CreateRequest { 13 | message User { 14 | string name = 1; 15 | string email = 2; 16 | string password = 3; 17 | } 18 | 19 | User user = 1; 20 | } 21 | 22 | message UpdateRequest { 23 | message User { 24 | string uuid = 1; 25 | string name = 2; 26 | string password = 4; 27 | } 28 | 29 | User user = 1; 30 | } -------------------------------------------------------------------------------- /proto/users/v1/response.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package users.v1.response; 4 | 5 | option php_namespace = "GRPC\\Services\\Users\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Users\\v1"; 7 | 8 | import "users/v1/message.proto"; 9 | 10 | message GetResponse { 11 | users.v1.dto.User user = 1; 12 | } 13 | 14 | message CreateResponse { 15 | users.v1.dto.User user = 1; 16 | } 17 | 18 | message UpdateResponse { 19 | users.v1.dto.User user = 1; 20 | } -------------------------------------------------------------------------------- /proto/users/v1/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package users.v1; 4 | 5 | option php_namespace = "GRPC\\Services\\Users\\v1"; 6 | option php_metadata_namespace = "GRPC\\ProtobufMetadata\\Users\\v1"; 7 | 8 | import "users/v1/request.proto"; 9 | import "users/v1/response.proto"; 10 | 11 | service UsersService { 12 | rpc Get (users.v1.request.GetRequest) returns (users.v1.response.GetResponse) { 13 | } 14 | 15 | rpc Create (users.v1.request.CreateRequest) returns (users.v1.response.CreateResponse) { 16 | } 17 | 18 | rpc Update (users.v1.request.UpdateRequest) returns (users.v1.response.UpdateResponse) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /subscriptions/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | 14 | [*.yaml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /subscriptions/.env.sample: -------------------------------------------------------------------------------- 1 | # Environment (prod or local) 2 | APP_ENV=local 3 | 4 | # Debug mode set to TRUE disables view caching and enables higher verbosity 5 | DEBUG=true 6 | 7 | # Verbosity level 8 | VERBOSITY_LEVEL=verbose # basic, verbose, debug 9 | 10 | # Set to an application specific value, used to encrypt/decrypt cookies etc 11 | ENCRYPTER_KEY={encrypt-key} 12 | 13 | # Monolog 14 | MONOLOG_DEFAULT_CHANNEL=default # Use "roadrunner" channel if you want to use RoadRunner logger 15 | MONOLOG_DEFAULT_LEVEL=DEBUG # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY 16 | 17 | # Telemetry 18 | TELEMETRY_DRIVER=null 19 | 20 | # Set to TRUE to disable confirmation in `migrate` commands 21 | SAFE_MIGRATIONS=true 22 | 23 | # Database 24 | DB_CONNECTION=sqlite 25 | 26 | # Cycle Bridge (Don't forget to set `CYCLE_SCHEMA_CACHE` to `true` in production) 27 | CYCLE_SCHEMA_CACHE=false 28 | CYCLE_SCHEMA_WARMUP=false 29 | -------------------------------------------------------------------------------- /subscriptions/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | runtime 4 | rr* 5 | protoc-gen-php-grpc* 6 | .env 7 | .phpunit.result.cache 8 | .phpunit.cache 9 | .deptrac.cache 10 | .phpunit.cache/ 11 | -------------------------------------------------------------------------------- /subscriptions/.rr-prod.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | rpc: 4 | listen: 'tcp://127.0.0.1:6001' 5 | 6 | server: 7 | command: 'php app.php' 8 | relay: pipes 9 | 10 | logs: 11 | level: ${RR_LOG_LEVEL:-warn} 12 | 13 | temporal: 14 | address: 'temporal:7233' 15 | -------------------------------------------------------------------------------- /subscriptions/.rr.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | rpc: 4 | listen: 'tcp://127.0.0.1:6001' 5 | 6 | server: 7 | command: 'php app.php' 8 | relay: pipes 9 | 10 | logs: 11 | level: ${RR_LOG_LEVEL:-debug} 12 | 13 | temporal: 14 | address: 'temporal:7233' 15 | -------------------------------------------------------------------------------- /subscriptions/.styleci.yml: -------------------------------------------------------------------------------- 1 | risky: false 2 | preset: psr12 3 | enabled: 4 | # Risky Fixers 5 | # - declare_strict_types 6 | # - void_return 7 | - ordered_class_elements 8 | - linebreak_after_opening_tag 9 | - single_quote 10 | - no_blank_lines_after_phpdoc 11 | - unary_operator_spaces 12 | - no_useless_else 13 | - no_useless_return 14 | - trailing_comma_in_multiline_array 15 | finder: 16 | exclude: 17 | - "Tests" 18 | - "installer/Application/Common/resources/tests" 19 | - "installer/Module/RoadRunnerBridge/resources/config" 20 | -------------------------------------------------------------------------------- /subscriptions/app.php: -------------------------------------------------------------------------------- 1 | __DIR__], 22 | )->run(); 23 | 24 | if ($app === null) { 25 | exit(255); 26 | } 27 | 28 | $code = (int)$app->serve(); 29 | exit($code); 30 | -------------------------------------------------------------------------------- /subscriptions/app/config/database.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'default' => null, 10 | 'drivers' => [ 11 | // 'runtime' => 'stdout' 12 | ], 13 | ], 14 | 15 | 'default' => 'default', 16 | 17 | 'databases' => [ 18 | 'default' => [ 19 | 'driver' => env('DB_CONNECTION', 'pgsql'), 20 | ], 21 | ], 22 | 23 | 'drivers' => [ 24 | 'pgsql' => new Config\PostgresDriverConfig( 25 | connection: new Config\Postgres\TcpConnectionConfig( 26 | database: env('DB_DATABASE', 'spiral'), 27 | host: env('DB_HOST', '127.0.0.1'), 28 | port: (int) env('DB_PORT', 5432), 29 | user: env('DB_USERNAME', 'postgres'), 30 | password: env('DB_PASSWORD', ''), 31 | ), 32 | schema: 'public', 33 | queryCache: true, 34 | ), 35 | ], 36 | ]; 37 | -------------------------------------------------------------------------------- /subscriptions/app/config/migration.php: -------------------------------------------------------------------------------- 1 | directory('app') . 'migrations/', 9 | 10 | 'table' => 'migrations', 11 | 12 | 'strategy' => MultipleFilesStrategy::class, 13 | 14 | 'safe' => env('APP_ENV') === 'local', 15 | ]; 16 | -------------------------------------------------------------------------------- /subscriptions/app/config/scaffolder.php: -------------------------------------------------------------------------------- 1 | 'App', 15 | 16 | 'declarations' => [ 17 | Declaration\BootloaderDeclaration::TYPE => [ 18 | 'namespace' => 'Application\\Bootloader', 19 | ], 20 | Declaration\ConfigDeclaration::TYPE => [ 21 | 'namespace' => 'Application\\Config', 22 | ], 23 | Declaration\ControllerDeclaration::TYPE => [ 24 | 'namespace' => 'Endpoint\\Web', 25 | ], 26 | Declaration\FilterDeclaration::TYPE => [ 27 | 'namespace' => 'Endpoint\\Web\\Filter', 28 | ], 29 | Declaration\MiddlewareDeclaration::TYPE => [ 30 | 'namespace' => 'Endpoint\\Web\\Middleware', 31 | ], 32 | Declaration\CommandDeclaration::TYPE => [ 33 | 'namespace' => 'Endpoint\\Console', 34 | ], 35 | Declaration\JobHandlerDeclaration::TYPE => [ 36 | 'namespace' => 'Endpoint\\Job', 37 | ], 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /subscriptions/app/config/sentry.php: -------------------------------------------------------------------------------- 1 | table('subscriptions') 16 | ->addColumn('created_at', 'datetime', [ 17 | 'nullable' => false, 18 | 'defaultValue' => 'CURRENT_TIMESTAMP', 19 | 'withTimezone' => false, 20 | ]) 21 | ->addColumn('updated_at', 'datetime', ['nullable' => false, 'defaultValue' => null, 'withTimezone' => false], 22 | ) 23 | ->addColumn('uuid', 'uuid', ['nullable' => false, 'defaultValue' => null]) 24 | ->addColumn('name', 'string', ['nullable' => false, 'defaultValue' => null, 'unique' => true, 'size' => 255], 25 | ) 26 | ->addColumn('trial_days', 'integer', ['nullable' => false, 'defaultValue' => null]) 27 | ->addColumn('price', 'double', ['nullable' => false, 'defaultValue' => null]) 28 | ->setPrimaryKeys(['uuid']) 29 | ->create(); 30 | } 31 | 32 | public function down(): void 33 | { 34 | $this->table('subscriptions')->drop(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /subscriptions/app/src/Application/Assert.php: -------------------------------------------------------------------------------- 1 | createdAt = $now; 39 | $this->updatedAt = $now; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /subscriptions/app/src/Domain/Subscription/SubscriptionRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | uuid; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /subscriptions/app/src/Endpoint/Console/SeedSubscriptionsCommand.php: -------------------------------------------------------------------------------- 1 | persist( 23 | new Subscription( 24 | uuid: Uuid::generate(), 25 | name: 'Basic', 26 | price: 5.0, 27 | trialDays: 7, 28 | ), 29 | ) 30 | ->persist( 31 | new Subscription( 32 | uuid: Uuid::generate(), 33 | name: 'Pro', 34 | price: 20.0, 35 | trialDays: 0, 36 | ), 37 | ) 38 | ->run(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /subscriptions/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | tests/Unit 18 | 19 | 20 | tests/Feature 21 | 22 | 23 | 24 | 25 | app/src 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /subscriptions/psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /subscriptions/tests/App/TestKernel.php: -------------------------------------------------------------------------------- 1 | beforeBooting(static function (ConfiguratorInterface $config): void { 20 | if (!$config->exists('session')) { 21 | return; 22 | } 23 | 24 | $config->modify('session', new Set('handler', null)); 25 | }); 26 | 27 | parent::setUp(); 28 | 29 | $container = $this->getContainer(); 30 | 31 | if ($container->has(TranslatorInterface::class)) { 32 | $container->get(TranslatorInterface::class)->setLocale('en'); 33 | } 34 | } 35 | 36 | public function createAppInstance(Container $container = new Container()): TestableKernelInterface 37 | { 38 | return TestKernel::create( 39 | directories: $this->defineDirectories( 40 | $this->rootDirectory(), 41 | ), 42 | container: $container, 43 | ); 44 | } 45 | 46 | protected function tearDown(): void 47 | { 48 | // Uncomment this line if you want to clean up runtime directory. 49 | // $this->cleanUpRuntimeDirectory(); 50 | } 51 | 52 | public function rootDirectory(): string 53 | { 54 | return __DIR__ . '/..'; 55 | } 56 | 57 | public function defineDirectories(string $root): array 58 | { 59 | return [ 60 | 'root' => $root, 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /subscriptions/tests/Unit/DemoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($expected); 17 | $this->assertFalse($actual); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /users/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | 14 | [*.yaml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /users/.env.sample: -------------------------------------------------------------------------------- 1 | # Environment (prod or local) 2 | APP_ENV=local 3 | 4 | # Debug mode set to TRUE disables view caching and enables higher verbosity 5 | DEBUG=true 6 | 7 | # Verbosity level 8 | VERBOSITY_LEVEL=verbose # basic, verbose, debug 9 | 10 | # Set to an application specific value, used to encrypt/decrypt cookies etc 11 | ENCRYPTER_KEY={encrypt-key} 12 | 13 | # Monolog 14 | MONOLOG_DEFAULT_CHANNEL=default # Use "roadrunner" channel if you want to use RoadRunner logger 15 | MONOLOG_DEFAULT_LEVEL=DEBUG # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY 16 | 17 | # Telemetry 18 | TELEMETRY_DRIVER=null 19 | 20 | # Set to TRUE to disable confirmation in `migrate` commands 21 | SAFE_MIGRATIONS=true 22 | 23 | # Database 24 | DB_CONNECTION=sqlite 25 | 26 | # Cycle Bridge (Don't forget to set `CYCLE_SCHEMA_CACHE` to `true` in production) 27 | CYCLE_SCHEMA_CACHE=false 28 | CYCLE_SCHEMA_WARMUP=false 29 | -------------------------------------------------------------------------------- /users/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | runtime 4 | rr* 5 | protoc-gen-php-grpc* 6 | .env 7 | .phpunit.result.cache 8 | .phpunit.cache 9 | .deptrac.cache 10 | .phpunit.cache/ 11 | -------------------------------------------------------------------------------- /users/.rr-prod.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | rpc: 4 | listen: 'tcp://127.0.0.1:6001' 5 | 6 | include: 7 | - ./.rr/.rr.grpc.yaml 8 | - ./.rr/.rr.otel.yaml 9 | 10 | server: 11 | command: 'php app.php' 12 | relay: pipes 13 | 14 | logs: 15 | level: ${RR_LOG_LEVEL:-debug} 16 | 17 | temporal: 18 | address: 'temporal:7233' 19 | -------------------------------------------------------------------------------- /users/.rr.otel.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | otel: 4 | insecure: true 5 | compress: false 6 | client: http 7 | exporter: otlp 8 | service_name: ${OTEL_SERVICE_NAME:-php-user} 9 | service_version: ${APP_VERSION:-1.0.0} 10 | endpoint: ms-collector:4318 11 | -------------------------------------------------------------------------------- /users/.rr.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | rpc: 4 | listen: 'tcp://127.0.0.1:6001' 5 | 6 | include: 7 | - ./.rr/.rr.grpc.yaml 8 | 9 | server: 10 | command: 'php app.php' 11 | relay: pipes 12 | -------------------------------------------------------------------------------- /users/.rr/.rr.grpc.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | grpc: 4 | listen: 'tcp://0.0.0.0:9001' 5 | proto: 6 | - ../../proto/users/v1/service.proto 7 | -------------------------------------------------------------------------------- /users/.rr/.rr.otel.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | #otel: 4 | # insecure: true 5 | # compress: false 6 | # client: http 7 | # exporter: otlp 8 | # service_name: ${OTEL_SERVICE_NAME:-php-user} 9 | # service_version: ${APP_VERSION:-1.0.0} 10 | # endpoint: ms-collector:4318 11 | -------------------------------------------------------------------------------- /users/.styleci.yml: -------------------------------------------------------------------------------- 1 | risky: false 2 | preset: psr12 3 | enabled: 4 | # Risky Fixers 5 | # - declare_strict_types 6 | # - void_return 7 | - ordered_class_elements 8 | - linebreak_after_opening_tag 9 | - single_quote 10 | - no_blank_lines_after_phpdoc 11 | - unary_operator_spaces 12 | - no_useless_else 13 | - no_useless_return 14 | - trailing_comma_in_multiline_array 15 | finder: 16 | exclude: 17 | - "Tests" 18 | - "installer/Application/Common/resources/tests" 19 | - "installer/Module/RoadRunnerBridge/resources/config" 20 | -------------------------------------------------------------------------------- /users/app.php: -------------------------------------------------------------------------------- 1 | __DIR__], 22 | )->run(); 23 | 24 | if ($app === null) { 25 | exit(255); 26 | } 27 | 28 | $code = (int)$app->serve(); 29 | exit($code); 30 | -------------------------------------------------------------------------------- /users/app/config/database.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'default' => null, 10 | 'drivers' => [ 11 | // 'runtime' => 'stdout' 12 | ], 13 | ], 14 | 15 | 'default' => 'default', 16 | 17 | 'databases' => [ 18 | 'default' => [ 19 | 'driver' => env('DB_CONNECTION', 'pgsql'), 20 | ], 21 | ], 22 | 23 | 'drivers' => [ 24 | 'pgsql' => new Config\PostgresDriverConfig( 25 | connection: new Config\Postgres\TcpConnectionConfig( 26 | database: env('DB_DATABASE', 'spiral'), 27 | host: env('DB_HOST', '127.0.0.1'), 28 | port: (int) env('DB_PORT', 5432), 29 | user: env('DB_USERNAME', 'postgres'), 30 | password: env('DB_PASSWORD', ''), 31 | ), 32 | schema: 'public', 33 | queryCache: true, 34 | ), 35 | ], 36 | ]; 37 | -------------------------------------------------------------------------------- /users/app/config/grpc.php: -------------------------------------------------------------------------------- 1 | directory('root') . 'protoc-gen-php-grpc', 7 | 'generatedPath' => directory('root') . '/generated', 8 | 'namespace' => 'GRPC', 9 | 'services' => [ 10 | directory('root') . '/../proto/users/v1/service.proto', 11 | directory('root') . '/../proto/auth/v1/service.proto', 12 | directory('root') . '/../proto/payment/v1/service.proto', 13 | directory('root') . '/../proto/common/v1/message.proto', 14 | ], 15 | 'servicesBasePath' => directory('root') . '/../proto', 16 | 'interceptors' => [ 17 | \App\Endpoint\GRPC\Interceptor\HandleExceptionsInterceptor::class, 18 | \Internal\Shared\gRPC\Interceptors\Incoming\ContextInterceptor::class, 19 | //\App\Endpoint\GRPC\Interceptor\GuardInterceptor::class, 20 | //\Internal\Shared\gRPC\Interceptors\Incoming\OpenTelemetryInterceptor::class, 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /users/app/config/grpcServices.php: -------------------------------------------------------------------------------- 1 | [ 7 | \Internal\Shared\gRPC\Interceptors\Outgoing\SendTraceContextInterceptor::class, 8 | ], 9 | ]; 10 | -------------------------------------------------------------------------------- /users/app/config/migration.php: -------------------------------------------------------------------------------- 1 | directory('app') . 'migrations/', 10 | 11 | 'table' => 'migrations', 12 | 13 | 'strategy' => MultipleFilesStrategy::class, 14 | 15 | 'safe' => env('APP_ENV') === 'local', 16 | ]; 17 | -------------------------------------------------------------------------------- /users/app/config/scaffolder.php: -------------------------------------------------------------------------------- 1 | 'App', 15 | 16 | 'declarations' => [ 17 | Declaration\BootloaderDeclaration::TYPE => [ 18 | 'namespace' => 'Application\\Bootloader', 19 | ], 20 | Declaration\ConfigDeclaration::TYPE => [ 21 | 'namespace' => 'Application\\Config', 22 | ], 23 | Declaration\ControllerDeclaration::TYPE => [ 24 | 'namespace' => 'Endpoint\\Web', 25 | ], 26 | Declaration\FilterDeclaration::TYPE => [ 27 | 'namespace' => 'Endpoint\\Web\\Filter', 28 | ], 29 | Declaration\MiddlewareDeclaration::TYPE => [ 30 | 'namespace' => 'Endpoint\\Web\\Middleware', 31 | ], 32 | Declaration\CommandDeclaration::TYPE => [ 33 | 'namespace' => 'Endpoint\\Console', 34 | ], 35 | Declaration\JobHandlerDeclaration::TYPE => [ 36 | 'namespace' => 'Endpoint\\Job', 37 | ], 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /users/app/config/sentry.php: -------------------------------------------------------------------------------- 1 | table('users') 16 | ->addColumn('uuid', 'uuid', ['nullable' => false, 'defaultValue' => null]) 17 | ->addColumn( 18 | 'email', 19 | 'string', 20 | ['nullable' => false, 'defaultValue' => null, 'unique' => true, 'size' => 255], 21 | ) 22 | ->addColumn('password', 'string', ['nullable' => false, 'defaultValue' => null, 'size' => 64]) 23 | ->setPrimaryKeys(['uuid']) 24 | ->create(); 25 | } 26 | 27 | public function down(): void 28 | { 29 | $this->table('users')->drop(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /users/app/migrations/20240529.183200_0_0_default_change_users_add_fist_name_add_last_name_add_language.php: -------------------------------------------------------------------------------- 1 | table('users') 16 | ->addColumn('fist_name', 'string', ['nullable' => true, 'defaultValue' => null, 'size' => 255]) 17 | ->addColumn('last_name', 'string', ['nullable' => true, 'defaultValue' => null, 'size' => 255]) 18 | ->addColumn('language', 'string', ['nullable' => false, 'defaultValue' => 'ru', 'size' => 255]) 19 | ->update(); 20 | } 21 | 22 | public function down(): void 23 | { 24 | $this->table('users') 25 | ->dropColumn('fist_name') 26 | ->dropColumn('last_name') 27 | ->dropColumn('language') 28 | ->update(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /users/app/migrations/20240529.191748_0_0_default_change_users_add_created_at_add_updated_at.php: -------------------------------------------------------------------------------- 1 | table('users') 16 | ->addColumn('created_at', 'datetime', [ 17 | 'nullable' => false, 18 | 'defaultValue' => 'CURRENT_TIMESTAMP', 19 | 'withTimezone' => false, 20 | ]) 21 | ->addColumn( 22 | 'updated_at', 23 | 'datetime', 24 | ['nullable' => true, 'defaultValue' => null, 'withTimezone' => false], 25 | ) 26 | ->update(); 27 | } 28 | 29 | public function down(): void 30 | { 31 | $this->table('users') 32 | ->dropColumn('created_at') 33 | ->dropColumn('updated_at') 34 | ->update(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /users/app/migrations/20240604.183640_0_0_default_create_auth_tokens.php: -------------------------------------------------------------------------------- 1 | table('auth_tokens') 16 | ->addColumn('id', 'string', ['nullable' => false, 'defaultValue' => null, 'size' => 64]) 17 | ->addColumn('hashed_value', 'string', ['nullable' => false, 'defaultValue' => null, 'size' => 128]) 18 | ->addColumn('created_at', 'datetime', ['nullable' => false, 'defaultValue' => null, 'withTimezone' => false]) 19 | ->addColumn('expires_at', 'datetime', ['nullable' => true, 'defaultValue' => null, 'withTimezone' => false]) 20 | ->addColumn('payload', 'binary', ['nullable' => false, 'defaultValue' => null]) 21 | ->setPrimaryKeys(['id']) 22 | ->create(); 23 | } 24 | 25 | public function down(): void 26 | { 27 | $this->table('auth_tokens')->drop(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /users/app/migrations/20240708.150058_0_0_default_change_users_add_email_verified_at_add_kyc_verified_at_add_subscription_uuid_alter_updated_at.php: -------------------------------------------------------------------------------- 1 | table('users') 16 | ->addColumn( 17 | 'email_verified_at', 18 | 'datetime', 19 | ['nullable' => true, 'defaultValue' => null, 'withTimezone' => false], 20 | ) 21 | ->addColumn( 22 | 'kyc_verified_at', 23 | 'datetime', 24 | ['nullable' => true, 'defaultValue' => null, 'withTimezone' => false], 25 | ) 26 | ->addColumn('subscription_uuid', 'uuid', ['nullable' => true, 'defaultValue' => null]) 27 | ->alterColumn( 28 | 'updated_at', 29 | 'datetime', 30 | ['nullable' => false, 'defaultValue' => null, 'withTimezone' => false], 31 | ) 32 | ->update(); 33 | } 34 | 35 | public function down(): void 36 | { 37 | $this->table('users') 38 | ->alterColumn( 39 | 'updated_at', 40 | 'timestamp', 41 | ['nullable' => true, 'defaultValue' => null, 'withTimezone' => false], 42 | ) 43 | ->dropColumn('email_verified_at') 44 | ->dropColumn('kyc_verified_at') 45 | ->dropColumn('subscription_uuid') 46 | ->update(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /users/app/src/Application/Assert.php: -------------------------------------------------------------------------------- 1 | bind(AuthServiceInterface::class, AuthService::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /users/app/src/Application/Bootloader/AuthBootloader.php: -------------------------------------------------------------------------------- 1 | OrmActorProvider::class 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /users/app/src/Application/Bootloader/PersistenceBootloader.php: -------------------------------------------------------------------------------- 1 | static fn( 24 | ORMInterface $orm, 25 | ): UserRepositoryInterface => new UserRepository(new Select($orm, User::ROLE)), 26 | 27 | UserFactoryInterface::class => UserFactory::class, 28 | 29 | UniqueEmailSpecificationInterface::class => UniqueEmailSpecification::class, 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /users/app/src/Application/Bootloader/UserBootloader.php: -------------------------------------------------------------------------------- 1 | PasswordHasher::class, 19 | UserServiceInterface::class => UserService::class, 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /users/app/src/Application/Exception/AuthException.php: -------------------------------------------------------------------------------- 1 | getPayload()['userId'] ?? null; 21 | 22 | if ($userId === null) { 23 | return null; 24 | } 25 | 26 | return $this->users->getByUuid(Uuid::fromString($userId)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /users/app/src/Domain/User/PasswordHasherInterface.php: -------------------------------------------------------------------------------- 1 | fistName === null || $this->lastName === null) { 25 | return null; 26 | } 27 | 28 | return \trim($this->fistName . ' ' . $this->lastName); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /users/app/src/Domain/User/Specification/UniqueEmailSpecificationInterface.php: -------------------------------------------------------------------------------- 1 | createdAt = $now; 51 | $this->updatedAt = $now; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /users/app/src/Domain/User/UserFactoryInterface.php: -------------------------------------------------------------------------------- 1 | email; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /users/app/src/Domain/User/ValueObject/Password.php: -------------------------------------------------------------------------------- 1 | password; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /users/app/src/Domain/User/ValueObject/PasswordHash.php: -------------------------------------------------------------------------------- 1 | hash; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /users/app/src/Domain/User/ValueObject/Uuid.php: -------------------------------------------------------------------------------- 1 | uuid; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/Console/RegisterUserCommand.php: -------------------------------------------------------------------------------- 1 | newWorkflowStub( 37 | class: RegisterUserWorkflow::class, // user.register.workflow 38 | options: WorkflowOptions::new() 39 | ->withTaskQueue(TaskQueue::UserRegistration) 40 | ->withWorkflowId((string) $userUuid) 41 | ->withWorkflowIdReusePolicy(IdReusePolicy::RejectDuplicate), 42 | ); 43 | 44 | // try { 45 | $result = $workflows->start($wf, new RegisterRequest([ 46 | 'email' => $this->email, 47 | 'password' => $this->password, 48 | 'name' => 'John Doe', 49 | ]))->getResult(User::class); 50 | // } catch (\Throwable $e) { 51 | // dump(ExceptionHelper::convertToRealException($e)); 52 | // return; 53 | // } 54 | 55 | $runWf = $workflows->newRunningWorkflowStub(RegisterUserWorkflow::class, (string) $userUuid); 56 | $runWf->subscriptionPaid('transaction-id-1234'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/GRPC/Exception/UnauthorizedException.php: -------------------------------------------------------------------------------- 1 | callAction($controller, $action, $parameters); 28 | } catch (\Throwable $e) { 29 | $this->reporter->report($e); 30 | throw new GRPCException( 31 | message: $e->getMessage(), 32 | code: StatusCode::NOT_FOUND, 33 | details: [ 34 | new Exception([ 35 | 'message' => $e->getMessage(), 36 | 'code' => $e->getCode(), 37 | 'class' => $e::class, 38 | ]), 39 | ], 40 | previous: $e, 41 | ); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/GRPC/Mapper/TimestampMapper.php: -------------------------------------------------------------------------------- 1 | fromDateTime(\DateTime::createFromInterface($dateTime)); 17 | 18 | return $timestamp; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/GRPC/Mapper/UserService/UserMapper.php: -------------------------------------------------------------------------------- 1 | (string) $user->uuid, 20 | 'email' => (string) $user->email, 21 | 'name' => (string) $user->profile->fullName(), 22 | 'created_at' => $this->timestamps->toMessage($user->createdAt), 23 | // 'updated_at' => $this->timestamps->toMessage($user->updatedAt), 24 | ]; 25 | 26 | $updatedAt = $this->timestamps->toMessage($user->updatedAt); 27 | 28 | if ($updatedAt !== null) { 29 | $data['updated_at'] = $updatedAt; 30 | } 31 | 32 | return new \GRPC\Services\Users\v1\User($data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/GRPC/Service/UserService.php: -------------------------------------------------------------------------------- 1 | usersService->getUser(Uuid::fromString($in->getUuid())); 34 | 35 | return new GetResponse(); 36 | } 37 | 38 | #[Guarded] 39 | public function Create(GRPC\ContextInterface $ctx, CreateRequest $in): CreateResponse 40 | { 41 | // TODO: Implement Create() method. 42 | } 43 | 44 | #[Guarded] 45 | public function Update(GRPC\ContextInterface $ctx, UpdateRequest $in): UpdateResponse 46 | { 47 | // TODO: Implement Update() method. 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/Temporal/User/EmailVerificationWorkflow.php: -------------------------------------------------------------------------------- 1 | notifications = Workflow::newActivityStub( 25 | NotificationsActivity::class, 26 | $this->activityOptions( 27 | taskQueue: TaskQueue::Notifications, 28 | timeout: CarbonInterval::minute(), 29 | retryAttempts: 3, 30 | ), 31 | ); 32 | } 33 | 34 | public function updateStatus(bool $status): void 35 | { 36 | $this->isVerified = $status; 37 | } 38 | 39 | public function currentStatus(): bool 40 | { 41 | return $this->isVerified; 42 | } 43 | 44 | public function process(UuidInterface $userUuid) 45 | { 46 | // 1. Send email verification link 47 | yield $this->notifications->sendVerificationLink($userUuid); 48 | 49 | // 2. Wait for verification 50 | yield Workflow::await(fn() => $this->isVerified); 51 | 52 | // 3. Send welcome email 53 | yield $this->notifications->emailVerified($userUuid); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/Temporal/User/RegisterWorkflow.php: -------------------------------------------------------------------------------- 1 | users = Workflow::newActivityStub( 30 | UserServiceActivity::class, 31 | $this->activityOptions( 32 | taskQueue: 'user.service', 33 | timeout: CarbonInterval::minute(), 34 | retryAttempts: 1, 35 | ), 36 | ); 37 | 38 | $this->notifications = Workflow::newActivityStub( 39 | NotificationsActivity::class, 40 | $this->activityOptions( 41 | taskQueue: 'notifications.service', 42 | timeout: CarbonInterval::minutes(5), 43 | retryAttempts: 3, 44 | ), 45 | ); 46 | } 47 | 48 | #[WorkflowMethod(name: 'RegisterUser')] 49 | public function register(RegisterRequest $request) 50 | { 51 | // 1. Create user in the database 52 | /** @var User $user */ 53 | // user.service.register 54 | $user = yield $this->users->register($request); 55 | 56 | // 2. Create auth token 57 | // $token = yield $this->auth->createToken($user->uuid); 58 | 59 | // 3. Create user notification settings 60 | // yield $this->notifications->createSettings($user); 61 | 62 | // 2. Send email to the user 63 | // yield $this->notifications->sendWelcomeEmail($user); 64 | 65 | return $user; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /users/app/src/Endpoint/Temporal/User/UserServiceActivity.php: -------------------------------------------------------------------------------- 1 | service->register( 30 | Email::create($request->getEmail()), 31 | Password::create($request->getPassword()), 32 | ); 33 | 34 | $createdAt = new Timestamp(); 35 | $createdAt->fromDateTime(\DateTime::createFromInterface($user->createdAt)); 36 | 37 | $updatedAt = new Timestamp(); 38 | if ($user->updatedAt !== null) { 39 | $updatedAt->fromDateTime(\DateTime::createFromInterface($user->updatedAt)); 40 | } 41 | 42 | return new User([ 43 | 'uuid' => (string) $user->uuid, 44 | 'name' => (string) $user->profile->fullName(), 45 | 'email' => (string) $user->email, 46 | 'created_at' => $createdAt, 47 | 'updated_at' => $updatedAt, 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /users/app/src/Infrastructure/CycleOrm/Factory/UserFactory.php: -------------------------------------------------------------------------------- 1 | orm->make(User::class, [ 25 | 'uuid' => Uuid::generate(), 26 | 'email' => $email, 27 | 'password' => $this->passwords->hash($password), 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /users/app/src/Infrastructure/CycleOrm/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | select()->where('email', $email)->fetchOne(); 19 | } 20 | 21 | public function findByUuid(Uuid $uuid): ?User 22 | { 23 | return $this->select()->where('uuid', $uuid)->fetchOne(); 24 | } 25 | 26 | public function getByEmail(Email $email): User 27 | { 28 | return $this->findByEmail($email) ?? throw new UserNotFoundException(); 29 | } 30 | 31 | public function getByUuid(Uuid $uuid): User 32 | { 33 | return $this->findByUuid($uuid) ?? throw new UserNotFoundException(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /users/app/src/Infrastructure/CycleOrm/Specification/UniqueEmailSpecification.php: -------------------------------------------------------------------------------- 1 | users->findByEmail($email) === null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /users/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | tests/Unit 18 | 19 | 20 | tests/Feature 21 | 22 | 23 | 24 | 25 | app/src 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /users/psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /users/tests/App/TestKernel.php: -------------------------------------------------------------------------------- 1 | beforeBooting(static function (ConfiguratorInterface $config): void { 20 | if (!$config->exists('session')) { 21 | return; 22 | } 23 | 24 | $config->modify('session', new Set('handler', null)); 25 | }); 26 | 27 | parent::setUp(); 28 | 29 | $container = $this->getContainer(); 30 | 31 | if ($container->has(TranslatorInterface::class)) { 32 | $container->get(TranslatorInterface::class)->setLocale('en'); 33 | } 34 | } 35 | 36 | public function createAppInstance(Container $container = new Container()): TestableKernelInterface 37 | { 38 | return TestKernel::create( 39 | directories: $this->defineDirectories( 40 | $this->rootDirectory(), 41 | ), 42 | container: $container, 43 | ); 44 | } 45 | 46 | protected function tearDown(): void 47 | { 48 | // Uncomment this line if you want to clean up runtime directory. 49 | // $this->cleanUpRuntimeDirectory(); 50 | } 51 | 52 | public function rootDirectory(): string 53 | { 54 | return __DIR__ . '/..'; 55 | } 56 | 57 | public function defineDirectories(string $root): array 58 | { 59 | return [ 60 | 'root' => $root, 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /users/tests/Unit/DemoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($expected); 17 | $this->assertFalse($actual); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | 14 | [*.yaml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /web/.env.sample: -------------------------------------------------------------------------------- 1 | # Environment (prod or local) 2 | APP_ENV=local 3 | 4 | # Debug mode set to TRUE disables view caching and enables higher verbosity 5 | DEBUG=true 6 | 7 | # Verbosity level 8 | VERBOSITY_LEVEL=verbose # basic, verbose, debug 9 | 10 | # Set to an application specific value, used to encrypt/decrypt cookies etc 11 | ENCRYPTER_KEY={encrypt-key} 12 | 13 | # Monolog 14 | MONOLOG_DEFAULT_CHANNEL=default # Use "roadrunner" channel if you want to use RoadRunner logger 15 | MONOLOG_DEFAULT_LEVEL=DEBUG # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY 16 | 17 | # Telemetry 18 | TELEMETRY_DRIVER=null 19 | 20 | # Session 21 | SESSION_LIFETIME=86400 22 | SESSION_COOKIE=sid 23 | 24 | # Authorization 25 | AUTH_TOKEN_TRANSPORT=cookie 26 | AUTH_TOKEN_STORAGE=session 27 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | runtime 4 | rr* 5 | protoc-gen-php-grpc* 6 | .env 7 | .phpunit.result.cache 8 | .phpunit.cache 9 | .deptrac.cache 10 | .phpunit.cache/ 11 | -------------------------------------------------------------------------------- /web/.rr-prod.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | rpc: 4 | listen: 'tcp://127.0.0.1:6001' 5 | 6 | http: 7 | address: '0.0.0.0:8080' 8 | middleware: 9 | - gzip 10 | - static 11 | static: 12 | dir: public 13 | forbid: 14 | - .php 15 | - .htaccess 16 | pool: 17 | num_workers: 1 18 | supervisor: 19 | max_worker_memory: 100 20 | 21 | server: 22 | command: 'php app.php' 23 | relay: pipes 24 | 25 | logs: 26 | level: ${RR_LOG_LEVEL:-debug} 27 | 28 | otel: 29 | insecure: true 30 | compress: false 31 | client: http 32 | exporter: otlp 33 | service_name: ${OTEL_SERVICE_NAME:-php-web} 34 | service_version: ${APP_VERSION:-1.0.0} 35 | endpoint: ms-collector:4318 36 | -------------------------------------------------------------------------------- /web/.rr.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | rpc: 4 | listen: 'tcp://127.0.0.1:6001' 5 | 6 | http: 7 | address: '0.0.0.0:8080' 8 | middleware: 9 | - gzip 10 | - static 11 | static: 12 | dir: public 13 | forbid: 14 | - .php 15 | - .htaccess 16 | pool: 17 | num_workers: 1 18 | debug: true 19 | supervisor: 20 | max_worker_memory: 100 21 | 22 | server: 23 | command: 'php app.php' 24 | relay: pipes 25 | -------------------------------------------------------------------------------- /web/.styleci.yml: -------------------------------------------------------------------------------- 1 | risky: false 2 | preset: psr12 3 | enabled: 4 | # Risky Fixers 5 | # - declare_strict_types 6 | # - void_return 7 | - ordered_class_elements 8 | - linebreak_after_opening_tag 9 | - single_quote 10 | - no_blank_lines_after_phpdoc 11 | - unary_operator_spaces 12 | - no_useless_else 13 | - no_useless_return 14 | - trailing_comma_in_multiline_array 15 | finder: 16 | exclude: 17 | - "Tests" 18 | - "installer/Application/Common/resources/tests" 19 | - "installer/Module/RoadRunnerBridge/resources/config" 20 | -------------------------------------------------------------------------------- /web/app.php: -------------------------------------------------------------------------------- 1 | __DIR__], 25 | )->run(); 26 | 27 | if ($app === null) { 28 | exit(255); 29 | } 30 | 31 | $code = (int)$app->serve(); 32 | exit($code); 33 | -------------------------------------------------------------------------------- /web/app/config/grpc-services.php: -------------------------------------------------------------------------------- 1 | [ 7 | \Internal\Shared\gRPC\Interceptors\Outgoing\SendTraceContextInterceptor::class, 8 | \App\Application\GRPC\Interceptor\AuthInterceptor::class, 9 | \Internal\Shared\gRPC\Interceptors\Incoming\ExceptionHandlerInterceptor::class, 10 | ], 11 | ]; 12 | -------------------------------------------------------------------------------- /web/app/config/grpc.php: -------------------------------------------------------------------------------- 1 | directory('root') . 'protoc-gen-php-grpc', 7 | 'generatedPath' => directory('root') . '/generated', 8 | 'namespace' => 'GRPC', 9 | 'services' => [ 10 | directory('root') . '/../proto/users/v1/service.proto', 11 | directory('root') . '/../proto/auth/v1/service.proto', 12 | directory('root') . '/../proto/payment/v1/service.proto', 13 | directory('root') . '/../proto/common/v1/message.proto', 14 | ], 15 | 'servicesBasePath' => directory('root') . '/../proto', 16 | ]; 17 | -------------------------------------------------------------------------------- /web/app/config/scaffolder.php: -------------------------------------------------------------------------------- 1 | 'App', 15 | 16 | 'declarations' => [ 17 | Declaration\BootloaderDeclaration::TYPE => [ 18 | 'namespace' => 'Application\\Bootloader', 19 | ], 20 | Declaration\ConfigDeclaration::TYPE => [ 21 | 'namespace' => 'Application\\Config', 22 | ], 23 | Declaration\ControllerDeclaration::TYPE => [ 24 | 'namespace' => 'Endpoint\\Web', 25 | ], 26 | Declaration\FilterDeclaration::TYPE => [ 27 | 'namespace' => 'Endpoint\\Web\\Filter', 28 | ], 29 | Declaration\MiddlewareDeclaration::TYPE => [ 30 | 'namespace' => 'Endpoint\\Web\\Middleware', 31 | ], 32 | Declaration\CommandDeclaration::TYPE => [ 33 | 'namespace' => 'Endpoint\\Console', 34 | ], 35 | Declaration\JobHandlerDeclaration::TYPE => [ 36 | 'namespace' => 'Endpoint\\Job', 37 | ], 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /web/app/config/session.php: -------------------------------------------------------------------------------- 1 | (int)env('SESSION_LIFETIME', 86400), 14 | 'cookie' => env('SESSION_COOKIE', 'sid'), 15 | 'secure' => true, 16 | 'sameSite' => null, 17 | 'handler' => new Autowire( 18 | FileHandler::class, 19 | [ 20 | 'directory' => directory('runtime') . 'session', 21 | 'lifetime' => (int)env('SESSION_LIFETIME', 86400), 22 | ] 23 | ), 24 | ]; 25 | -------------------------------------------------------------------------------- /web/app/src/Application/Auth/AuthKey.php: -------------------------------------------------------------------------------- 1 | key; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/app/src/Application/Auth/AuthKeyInterface.php: -------------------------------------------------------------------------------- 1 | [self::class, 'domainCore']]; 17 | protected const INTERCEPTORS = [ 18 | GuardInterceptor::class 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /web/app/src/Application/Bootloader/LoggingBootloader.php: -------------------------------------------------------------------------------- 1 | addHandler( 30 | channel: ErrorHandlerMiddleware::class, 31 | handler: $monolog->logRotate( 32 | directory('runtime') . 'logs/http.log', 33 | ), 34 | ); 35 | 36 | // app level errors 37 | $monolog->addHandler( 38 | channel: MonologConfig::DEFAULT_CHANNEL, 39 | handler: $monolog->logRotate( 40 | filename: directory('runtime') . 'logs/error.log', 41 | level: Logger::ERROR, 42 | maxFiles: 25, 43 | bubble: false, 44 | ), 45 | ); 46 | 47 | // debug and info messages via global LoggerInterface 48 | $monolog->addHandler( 49 | channel: MonologConfig::DEFAULT_CHANNEL, 50 | handler: $monolog->logRotate( 51 | filename: directory('runtime') . 'logs/debug.log', 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /web/app/src/Application/Bootloader/RoutesBootloader.php: -------------------------------------------------------------------------------- 1 | [ 38 | SimpleAuthMiddleware::class, 39 | ], 40 | ]; 41 | } 42 | 43 | protected function defineRoutes(RoutingConfigurator $routes): void 44 | { 45 | // Fallback route if no other route matched 46 | // Will show 404 page 47 | // $routes->default('/') 48 | // ->callable(function (ServerRequestInterface $r, ResponseInterface $response) { 49 | // return $response->withStatus(404)->withBody('Not found'); 50 | // }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /web/app/src/Application/Exception/NotFoundException.php: -------------------------------------------------------------------------------- 1 | withAuthToken($this->key->getKey()); 24 | 25 | return $core->callAction($controller, $action, $parameters); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Console/TestCommand.php: -------------------------------------------------------------------------------- 1 | Get( 22 | new Context([]), 23 | new \GRPC\Services\Users\v1\GetRequest([ 24 | 'uuid' => Uuid::uuid4()->toString() 25 | ]) 26 | ); 27 | 28 | trap($response); 29 | 30 | return 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Controller/Auth/LoginAction.php: -------------------------------------------------------------------------------- 1 | authService->Login( 22 | RequestContext::create(), 23 | new \GRPC\Services\Auth\v1\LoginRequest([ 24 | 'email' => $request->email, 25 | 'password' => $request->password, 26 | ]), 27 | ); 28 | 29 | return \json_decode( 30 | $response->getToken()->serializeToJsonString(), 31 | true, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Controller/User/CreateAction.php: -------------------------------------------------------------------------------- 1 | Create( 21 | new Context([]), 22 | new \GRPC\Services\Users\v1\CreateRequest([ 23 | 'user' => new User([ 24 | 'name' => $request->name, 25 | 'email' => $request->email, 26 | 'password' => $request->password, 27 | ]), 28 | ]), 29 | ); 30 | 31 | return \json_decode( 32 | $response->getUser()->serializeToJsonString(), 33 | true, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Controller/User/ShowAction.php: -------------------------------------------------------------------------------- 1 | ', methods: ['GET'])] 16 | public function __invoke( 17 | UsersServiceInterface $users, 18 | string $uuid, 19 | ): array { 20 | $response = $users->Get( 21 | RequestContext::create(), 22 | new \GRPC\Services\Users\v1\GetRequest([ 23 | 'uuid' => Uuid::fromString($uuid)->toString(), 24 | ]), 25 | ); 26 | 27 | return \json_decode( 28 | $response->getUser()->serializeToJsonString(), 29 | true, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Middleware/ErrorHandlerMiddleware.php: -------------------------------------------------------------------------------- 1 | handle($request); 21 | } catch (NotFoundException $e) { 22 | throw new \Spiral\Http\Exception\ClientException\NotFoundException(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Middleware/SimpleAuthMiddleware.php: -------------------------------------------------------------------------------- 1 | getHeaderLine('X-Auth-Token') ?? null; 26 | 27 | return $this->container->runScope([ 28 | AuthKeyInterface::class => new AuthKey($authKey), 29 | ], function () use ($request, $handler) { 30 | return $handler->handle($request); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Request/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'email'], 25 | 'password' => ['required', 'string'], 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/app/src/Endpoint/Http/Request/User/CreateRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'string'], 28 | 'email' => ['required', 'email'], 29 | 'password' => ['required', 'string'], 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/functions.php: -------------------------------------------------------------------------------- 1 | addDump( 17 | $dumper->dump((new VarCloner())->cloneVar($var), true) 18 | ); 19 | } 20 | 21 | throw $throwable; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | tests/Unit 18 | 19 | 20 | tests/Feature 21 | 22 | 23 | 24 | 25 | app/src 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-fart/grpc-microservices/df91325a22df18aea3ed00b7de7c88c019f8f7ef/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/styles/welcome.css: -------------------------------------------------------------------------------- 1 | .box { 2 | margin-bottom: 17px; 3 | padding: 46px 49px; 4 | background: rgba(255, 255, 255, 0.56); 5 | } 6 | .box .items { 7 | display: flex; 8 | justify-content: space-between; 9 | flex-wrap: wrap; 10 | row-gap: 33px; 11 | border-radius: 10px; 12 | } 13 | 14 | .item { 15 | width: calc(50% - 33px); 16 | } 17 | 18 | .item__title { 19 | margin: 0 0 11px; 20 | font-weight: 900; 21 | font-size: 12px; 22 | line-height: 150%; 23 | } 24 | 25 | .item__title a { 26 | color: inherit; 27 | text-decoration: none; 28 | transition: opacity .25s ease-out; 29 | } 30 | 31 | .item__title a:hover { 32 | opacity: .8; 33 | } 34 | 35 | .item__text { 36 | margin: 0; 37 | font-size: 12px; 38 | line-height: 150%; 39 | } 40 | 41 | .links { 42 | display: flex; 43 | justify-content: center; 44 | margin: 30px 0; 45 | } 46 | 47 | .links-item { 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | width: 40px; 52 | height: 40px; 53 | color: #4d5460; 54 | background-color: transparent; 55 | box-sizing: border-box; 56 | transition: all .25s ease-out 57 | } 58 | 59 | .links-item:hover { 60 | color: #161b22; 61 | } 62 | 63 | .links-item:not(:last-child) { 64 | margin-right: 6px 65 | } 66 | 67 | .discourse-path-1,.discourse-path-2 { 68 | fill: #fff; 69 | } 70 | 71 | .discourse-path-3 { 72 | fill: #4d5460; 73 | } 74 | -------------------------------------------------------------------------------- /web/tests/App/TestKernel.php: -------------------------------------------------------------------------------- 1 | beforeBooting(static function (ConfiguratorInterface $config): void { 20 | if (!$config->exists('session')) { 21 | return; 22 | } 23 | 24 | $config->modify('session', new Set('handler', null)); 25 | }); 26 | 27 | parent::setUp(); 28 | 29 | $container = $this->getContainer(); 30 | 31 | if ($container->has(TranslatorInterface::class)) { 32 | $container->get(TranslatorInterface::class)->setLocale('en'); 33 | } 34 | } 35 | 36 | public function createAppInstance(Container $container = new Container()): TestableKernelInterface 37 | { 38 | return TestKernel::create( 39 | directories: $this->defineDirectories( 40 | $this->rootDirectory(), 41 | ), 42 | container: $container, 43 | ); 44 | } 45 | 46 | protected function tearDown(): void 47 | { 48 | // Uncomment this line if you want to clean up runtime directory. 49 | // $this->cleanUpRuntimeDirectory(); 50 | } 51 | 52 | public function rootDirectory(): string 53 | { 54 | return __DIR__ . '/..'; 55 | } 56 | 57 | public function defineDirectories(string $root): array 58 | { 59 | return [ 60 | 'root' => $root, 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /web/tests/Unit/DemoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($expected); 17 | $this->assertFalse($actual); 18 | } 19 | } 20 | --------------------------------------------------------------------------------