diff --git a/.env.example b/.env.example index 4d3d446..b2a9f63 100644 --- a/.env.example +++ b/.env.example @@ -42,11 +42,6 @@ CACHE_STORE=database MEMCACHED_HOST=127.0.0.1 -REDIS_CLIENT=phpredis -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 - MAIL_MAILER=log MAIL_SCHEME=null MAIL_HOST=127.0.0.1 diff --git a/Dockerfile b/Dockerfile index b233f83..def0b8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,61 +1,82 @@ -FROM php:8.4.4-fpm-alpine AS builder +FROM php:8.4.4-fpm AS builder -# Install necessary build dependencies -RUN apk add --no-cache \ - mysql-dev \ - postgresql-dev \ +# Install system dependencies including protobuf +RUN apt-get update && apt-get install -y \ + supervisor \ + libpq-dev \ libzip-dev \ unzip \ git \ - openssl-dev \ - brotli-dev \ + curl \ + wget \ + libcurl4-openssl-dev \ + libssl-dev \ + pkg-config \ + protobuf-compiler \ + protobuf-compiler-grpc \ + libprotobuf-dev \ + build-essential \ autoconf \ - automake \ - make \ - gcc \ - g++ \ - libc-dev \ - pcre-dev \ - $PHPIZE_DEPS - -# Install PHP extensions -RUN docker-php-ext-install pdo_mysql pdo_pgsql zip \ - && docker-php-ext-configure pcntl --enable-pcntl \ - && docker-php-ext-install pcntl \ - && docker-php-ext-install opcache - -# Install Swoole via PECL -RUN pecl install swoole \ - && docker-php-ext-enable swoole \ - && pecl install redis \ - && docker-php-ext-enable redis - -FROM php:8.4.4-fpm-alpine AS production - -# Install runtime dependencies (required for the extensions to work) -RUN apk add --no-cache \ - mysql-client \ - postgresql-client \ - libzip \ - git - -# Copy PHP extensions from builder stage + libtool \ + netcat-openbsd \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Install PHP extensions (separate layer for better caching) +RUN docker-php-ext-install \ + pdo_mysql \ + zip \ + sockets \ + pcntl \ + bcmath \ + opcache + +# Copy backed up gRPC extension files +COPY grpc-backup/extensions/ /usr/local/lib/php/extensions/ +COPY grpc-backup/conf.d/ /usr/local/etc/php/conf.d/ + +# Install remaining PECL extensions +# docke + +FROM php:8.4.4-fpm AS production + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + supervisor \ + libpq-dev \ + libzip-dev \ + unzip \ + git \ + curl \ + libcurl4-openssl-dev \ + libssl-dev \ + pkg-config \ + protobuf-compiler \ + protobuf-compiler-grpc \ + libprotobuf-dev \ + netcat-openbsd \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Copy PHP extensions from builder COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/ COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/ -# Copy php.ini with OPcache settings -COPY php.ini /usr/local/etc/php/conf.d/opcache.ini +# Clean up PECL cache to reduce image size +RUN rm -rf /tmp/pear -# Install Composer -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +# Install Composer (separate layer) +COPY --from=composer:2.8 /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /var/www -# Copy composer files first for better caching +# Copy PHP configuration early (rarely changes) +COPY php.ini /usr/local/etc/php/conf.d/custom.ini + +# Copy composer files (for dependency caching) COPY composer.json composer.lock ./ -# Install dependencies (this layer will be cached if composer files don't change) # Install dependencies with production optimizations RUN composer install \ --no-dev \ @@ -65,16 +86,37 @@ RUN composer install \ --classmap-authoritative \ && composer clear-cache -# Copy application files + # Copy supervisor configuration +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +# Copy application files (most frequently changing layer - put last) + COPY . . -# Set permissions -RUN chown -R www-data:www-data /var/www \ +# Create necessary directories and set permissions +RUN mkdir -p storage/logs storage/framework/{cache,sessions,views} bootstrap/cache \ + && chown -R www-data:www-data /var/www \ && chmod -R 755 /var/www/storage \ && chmod -R 755 /var/www/bootstrap/cache -# Expose Port -EXPOSE 8000 +# Expose ports +EXPOSE 50051 8080 + +# Health check for gRPC service +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD nc -z localhost 50051 || exit 1 + +# Create startup script +RUN echo '#!/bin/bash\n\ +set -e\n\ +echo "Starting Laravel gRPC application..."\n\ +php artisan config:cache\n\ +php artisan route:cache\n\ +php artisan view:cache\n\ +php artisan migrate --force\n\ +php artisan storage:link\n\ +echo "Starting supervisor..."\n\ +exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf\n\ +' > /usr/local/bin/start.sh \ + && chmod +x /usr/local/bin/start.sh -# Start Laravel Octane with Swoole -CMD sh -c "php artisan migrate --force && php artisan storage:link && php artisan octane:start --server=swoole --host=0.0.0.0 --port=8000" +CMD ["/usr/local/bin/start.sh"] diff --git a/app/Console/Commands/GrpcServeCommand.php b/app/Console/Commands/GrpcServeCommand.php new file mode 100644 index 0000000..8c733f4 --- /dev/null +++ b/app/Console/Commands/GrpcServeCommand.php @@ -0,0 +1,40 @@ +option('host'); + $port = (int)$this->option('port'); + + $this->info("Starting gRPC User Service on {$host}:{$port}"); + + try { + $server = new RpcServer([]); + $server->addHttp2Port("{$host}:{$port}"); + + // Register the User service + $server->handle(app(\App\Grpc\Services\GatewayService::class)); + + $this->info('gRPC User Service started successfully'); + $this->info('Listening for requests...'); + + // Start the server (this will block) + $server->run(); + + } catch (\Exception $e) { + $this->error('Failed to start gRPC server: ' . $e->getMessage()); + return 1; + } + return 0; + } +} diff --git a/app/External_Apis/Apis/AIApi.php b/app/External_Apis/Apis/AIApi.php new file mode 100644 index 0000000..33d4c42 --- /dev/null +++ b/app/External_Apis/Apis/AIApi.php @@ -0,0 +1,19 @@ +injectUserId($data); + $data[] = $this->injectUserId(); $res = $this->OfferRepository->getAll($data); $data = $this->handlingResErrorAndMessage($res); if ($res->successful()) diff --git a/app/External_Apis/Services/ProductService.php b/app/External_Apis/Services/ProductService.php index 19fbf2a..58513d2 100644 --- a/app/External_Apis/Services/ProductService.php +++ b/app/External_Apis/Services/ProductService.php @@ -30,7 +30,7 @@ public function __construct( */ public function index(array $data): array { - $this->injectUserId($data); + $data[] = $this->injectUserId(); $res = $this->productRepository->getAll($data); $data = $this->handlingResErrorAndMessage($res); if ($res->successful()) diff --git a/app/Grpc/BytesRequest.php b/app/Grpc/BytesRequest.php new file mode 100644 index 0000000..17e9989 --- /dev/null +++ b/app/Grpc/BytesRequest.php @@ -0,0 +1,202 @@ +gateway.BytesRequest + */ +class BytesRequest extends \Google\Protobuf\Internal\Message +{ + /** + * Generated from protobuf field string service = 1; + */ + protected $service = ''; + /** + * Generated from protobuf field string method = 2; + */ + protected $method = ''; + /** + * Generated from protobuf field bytes payload = 3; + */ + protected $payload = ''; + /** + * "application/json", "application/protobuf", etc. + * + * Generated from protobuf field string content_type = 4; + */ + protected $content_type = ''; + /** + * Generated from protobuf field map headers = 5; + */ + private $headers; + /** + * Generated from protobuf field string request_id = 6; + */ + protected $request_id = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @type string $service + * @type string $method + * @type string $payload + * @type string $content_type + * "application/json", "application/protobuf", etc. + * @type array|\Google\Protobuf\Internal\MapField $headers + * @type string $request_id + * } + */ + public function __construct($data = NULL) { + \App\Grpc\Gateway\Apigw::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string service = 1; + * @return string + */ + public function getService() + { + return $this->service; + } + + /** + * Generated from protobuf field string service = 1; + * @param string $var + * @return $this + */ + public function setService($var) + { + GPBUtil::checkString($var, True); + $this->service = $var; + + return $this; + } + + /** + * Generated from protobuf field string method = 2; + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Generated from protobuf field string method = 2; + * @param string $var + * @return $this + */ + public function setMethod($var) + { + GPBUtil::checkString($var, True); + $this->method = $var; + + return $this; + } + + /** + * Generated from protobuf field bytes payload = 3; + * @return string + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Generated from protobuf field bytes payload = 3; + * @param string $var + * @return $this + */ + public function setPayload($var) + { + GPBUtil::checkString($var, False); + $this->payload = $var; + + return $this; + } + + /** + * "application/json", "application/protobuf", etc. + * + * Generated from protobuf field string content_type = 4; + * @return string + */ + public function getContentType() + { + return $this->content_type; + } + + /** + * "application/json", "application/protobuf", etc. + * + * Generated from protobuf field string content_type = 4; + * @param string $var + * @return $this + */ + public function setContentType($var) + { + GPBUtil::checkString($var, True); + $this->content_type = $var; + + return $this; + } + + /** + * Generated from protobuf field map headers = 5; + * @return \Google\Protobuf\Internal\MapField + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Generated from protobuf field map headers = 5; + * @param array|\Google\Protobuf\Internal\MapField $var + * @return $this + */ + public function setHeaders($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING); + $this->headers = $arr; + + return $this; + } + + /** + * Generated from protobuf field string request_id = 6; + * @return string + */ + public function getRequestId() + { + return $this->request_id; + } + + /** + * Generated from protobuf field string request_id = 6; + * @param string $var + * @return $this + */ + public function setRequestId($var) + { + GPBUtil::checkString($var, True); + $this->request_id = $var; + + return $this; + } + +} + diff --git a/app/Grpc/BytesResponse.php b/app/Grpc/BytesResponse.php new file mode 100644 index 0000000..b6e6228 --- /dev/null +++ b/app/Grpc/BytesResponse.php @@ -0,0 +1,247 @@ +gateway.BytesResponse + */ +class BytesResponse extends \Google\Protobuf\Internal\Message +{ + /** + * Generated from protobuf field bool success = 1; + */ + protected $success = false; + /** + * Generated from protobuf field int32 status_code = 2; + */ + protected $status_code = 0; + /** + * Generated from protobuf field bytes data = 3; + */ + protected $data = ''; + /** + * Generated from protobuf field string content_type = 4; + */ + protected $content_type = ''; + /** + * Generated from protobuf field .gateway.ErrorInfo error = 5; + */ + protected $error = null; + /** + * Generated from protobuf field map headers = 6; + */ + private $headers; + /** + * Generated from protobuf field string request_id = 7; + */ + protected $request_id = ''; + /** + * Generated from protobuf field .google.protobuf.Timestamp timestamp = 8; + */ + protected $timestamp = null; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @type bool $success + * @type int $status_code + * @type string $data + * @type string $content_type + * @type \App\Grpc\ErrorInfo $error + * @type array|\Google\Protobuf\Internal\MapField $headers + * @type string $request_id + * @type \Google\Protobuf\Timestamp $timestamp + * } + */ + public function __construct($data = NULL) { + \App\Grpc\Gateway\Apigw::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field bool success = 1; + * @return bool + */ + public function getSuccess() + { + return $this->success; + } + + /** + * Generated from protobuf field bool success = 1; + * @param bool $var + * @return $this + */ + public function setSuccess($var) + { + GPBUtil::checkBool($var); + $this->success = $var; + + return $this; + } + + /** + * Generated from protobuf field int32 status_code = 2; + * @return int + */ + public function getStatusCode() + { + return $this->status_code; + } + + /** + * Generated from protobuf field int32 status_code = 2; + * @param int $var + * @return $this + */ + public function setStatusCode($var) + { + GPBUtil::checkInt32($var); + $this->status_code = $var; + + return $this; + } + + /** + * Generated from protobuf field bytes data = 3; + * @return string + */ + public function getData() + { + return $this->data; + } + + /** + * Generated from protobuf field bytes data = 3; + * @param string $var + * @return $this + */ + public function setData($var) + { + GPBUtil::checkString($var, False); + $this->data = $var; + + return $this; + } + + /** + * Generated from protobuf field string content_type = 4; + * @return string + */ + public function getContentType() + { + return $this->content_type; + } + + /** + * Generated from protobuf field string content_type = 4; + * @param string $var + * @return $this + */ + public function setContentType($var) + { + GPBUtil::checkString($var, True); + $this->content_type = $var; + + return $this; + } + + /** + * Generated from protobuf field .gateway.ErrorInfo error = 5; + * @return \App\Grpc\ErrorInfo + */ + public function getError() + { + return $this->error; + } + + /** + * Generated from protobuf field .gateway.ErrorInfo error = 5; + * @param \App\Grpc\ErrorInfo $var + * @return $this + */ + public function setError($var) + { + GPBUtil::checkMessage($var, \App\Grpc\ErrorInfo::class); + $this->error = $var; + + return $this; + } + + /** + * Generated from protobuf field map headers = 6; + * @return \Google\Protobuf\Internal\MapField + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Generated from protobuf field map headers = 6; + * @param array|\Google\Protobuf\Internal\MapField $var + * @return $this + */ + public function setHeaders($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING); + $this->headers = $arr; + + return $this; + } + + /** + * Generated from protobuf field string request_id = 7; + * @return string + */ + public function getRequestId() + { + return $this->request_id; + } + + /** + * Generated from protobuf field string request_id = 7; + * @param string $var + * @return $this + */ + public function setRequestId($var) + { + GPBUtil::checkString($var, True); + $this->request_id = $var; + + return $this; + } + + /** + * Generated from protobuf field .google.protobuf.Timestamp timestamp = 8; + * @return \Google\Protobuf\Timestamp + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * Generated from protobuf field .google.protobuf.Timestamp timestamp = 8; + * @param \Google\Protobuf\Timestamp $var + * @return $this + */ + public function setTimestamp($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->timestamp = $var; + + return $this; + } + +} + diff --git a/app/Grpc/Controllers/AuthController.php b/app/Grpc/Controllers/AuthController.php new file mode 100644 index 0000000..d14d9df --- /dev/null +++ b/app/Grpc/Controllers/AuthController.php @@ -0,0 +1,36 @@ +setSpecifiedService($request, $this->defaultService); + return $this->grpcService->forwardGrpcToService($request); + } + + public function login(FlexibleRequest $request): FlexibleResponse + { + $this->setSpecifiedService($request, $this->defaultService); + return $this->grpcService->forwardGrpcToService($request); + } + + public function validateToken(FlexibleRequest $request): FlexibleResponse + { + $this->setSpecifiedService($request, $this->defaultService); + return $this->grpcService->forwardGrpcToService($request); + } + +} diff --git a/app/Grpc/Controllers/BaseGrpcController.php b/app/Grpc/Controllers/BaseGrpcController.php new file mode 100644 index 0000000..e381cd6 --- /dev/null +++ b/app/Grpc/Controllers/BaseGrpcController.php @@ -0,0 +1,20 @@ +getService(); + $services = explode('/', $service); + $service = $services[1] ?? $default ?? $services[0]; + \Log::info("Forwarding request to service: {$services[0]}/{$service}, method: {$request->getMethod()}"); + $request->setService($service); + } +} diff --git a/app/Grpc/Controllers/ProductController.php b/app/Grpc/Controllers/ProductController.php new file mode 100644 index 0000000..662e738 --- /dev/null +++ b/app/Grpc/Controllers/ProductController.php @@ -0,0 +1,43 @@ +getPayload()) { + $data = json_decode($payload->getValue(), true); + $data = array_merge($data, $this->productService->injectUserId()); + $request->setPayload($payload->setValue(json_encode($data))); + } + return $this->grpcService->forwardGrpcToService($request); + } + + public function show(FlexibleRequest $request): FlexibleResponse + { + return $this->grpcService->forwardGrpcToService($request); + } +} diff --git a/app/Grpc/Controllers/UserController.php b/app/Grpc/Controllers/UserController.php new file mode 100644 index 0000000..c3bd06f --- /dev/null +++ b/app/Grpc/Controllers/UserController.php @@ -0,0 +1,22 @@ +setSpecifiedService($request); + return $this->grpcService->forwardGrpcToService($request); + } + +} diff --git a/app/Grpc/ErrorInfo.php b/app/Grpc/ErrorInfo.php new file mode 100644 index 0000000..3b840b0 --- /dev/null +++ b/app/Grpc/ErrorInfo.php @@ -0,0 +1,141 @@ +gateway.ErrorInfo + */ +class ErrorInfo extends \Google\Protobuf\Internal\Message +{ + /** + * Generated from protobuf field string message = 1; + */ + protected $message = ''; + /** + * Generated from protobuf field string code = 2; + */ + protected $code = ''; + /** + * Generated from protobuf field repeated string details = 3; + */ + private $details; + /** + * Generated from protobuf field map metadata = 4; + */ + private $metadata; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @type string $message + * @type string $code + * @type string[]|\Google\Protobuf\Internal\RepeatedField $details + * @type array|\Google\Protobuf\Internal\MapField $metadata + * } + */ + public function __construct($data = NULL) { + \App\Grpc\Gateway\Apigw::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string message = 1; + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Generated from protobuf field string message = 1; + * @param string $var + * @return $this + */ + public function setMessage($var) + { + GPBUtil::checkString($var, True); + $this->message = $var; + + return $this; + } + + /** + * Generated from protobuf field string code = 2; + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * Generated from protobuf field string code = 2; + * @param string $var + * @return $this + */ + public function setCode($var) + { + GPBUtil::checkString($var, True); + $this->code = $var; + + return $this; + } + + /** + * Generated from protobuf field repeated string details = 3; + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getDetails() + { + return $this->details; + } + + /** + * Generated from protobuf field repeated string details = 3; + * @param string[]|\Google\Protobuf\Internal\RepeatedField $var + * @return $this + */ + public function setDetails($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING); + $this->details = $arr; + + return $this; + } + + /** + * Generated from protobuf field map metadata = 4; + * @return \Google\Protobuf\Internal\MapField + */ + public function getMetadata() + { + return $this->metadata; + } + + /** + * Generated from protobuf field map metadata = 4; + * @param array|\Google\Protobuf\Internal\MapField $var + * @return $this + */ + public function setMetadata($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING); + $this->metadata = $arr; + + return $this; + } + +} + diff --git a/app/Grpc/FlexibleRequest.php b/app/Grpc/FlexibleRequest.php new file mode 100644 index 0000000..f448e55 --- /dev/null +++ b/app/Grpc/FlexibleRequest.php @@ -0,0 +1,203 @@ +gateway.FlexibleRequest + */ +class FlexibleRequest extends \Google\Protobuf\Internal\Message +{ + /** + * Service identifier (user-service, product-service, etc.) + * + * Generated from protobuf field string service = 1; + */ + protected $service = ''; + /** + * Method/endpoint identifier + * + * Generated from protobuf field string method = 2; + */ + protected $method = ''; + /** + * Generic payload - can be any protobuf message + * + * Generated from protobuf field .google.protobuf.Any payload = 3; + */ + protected $payload = null; + /** + * Request metadata + * + * Generated from protobuf field map headers = 4; + */ + private $headers; + /** + * Optional request ID for tracing + * + * Generated from protobuf field string request_id = 5; + */ + protected $request_id = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @type string $service + * Service identifier (user-service, product-service, etc.) + * @type string $method + * Method/endpoint identifier + * @type \Google\Protobuf\Any $payload + * Generic payload - can be any protobuf message + * @type array|\Google\Protobuf\Internal\MapField $headers + * Request metadata + * @type string $request_id + * Optional request ID for tracing + * } + */ + public function __construct($data = NULL) { + \App\Grpc\Gateway\Apigw::initOnce(); + parent::__construct($data); + } + + /** + * Service identifier (user-service, product-service, etc.) + * + * Generated from protobuf field string service = 1; + * @return string + */ + public function getService() + { + return $this->service; + } + + /** + * Service identifier (user-service, product-service, etc.) + * + * Generated from protobuf field string service = 1; + * @param string $var + * @return $this + */ + public function setService($var) + { + GPBUtil::checkString($var, True); + $this->service = $var; + + return $this; + } + + /** + * Method/endpoint identifier + * + * Generated from protobuf field string method = 2; + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Method/endpoint identifier + * + * Generated from protobuf field string method = 2; + * @param string $var + * @return $this + */ + public function setMethod($var) + { + GPBUtil::checkString($var, True); + $this->method = $var; + + return $this; + } + + /** + * Generic payload - can be any protobuf message + * + * Generated from protobuf field .google.protobuf.Any payload = 3; + * @return \Google\Protobuf\Any + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Generic payload - can be any protobuf message + * + * Generated from protobuf field .google.protobuf.Any payload = 3; + * @param \Google\Protobuf\Any $var + * @return $this + */ + public function setPayload($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Any::class); + $this->payload = $var; + + return $this; + } + + /** + * Request metadata + * + * Generated from protobuf field map headers = 4; + * @return \Google\Protobuf\Internal\MapField + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Request metadata + * + * Generated from protobuf field map headers = 4; + * @param array|\Google\Protobuf\Internal\MapField $var + * @return $this + */ + public function setHeaders($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING); + $this->headers = $arr; + + return $this; + } + + /** + * Optional request ID for tracing + * + * Generated from protobuf field string request_id = 5; + * @return string + */ + public function getRequestId() + { + return $this->request_id; + } + + /** + * Optional request ID for tracing + * + * Generated from protobuf field string request_id = 5; + * @param string $var + * @return $this + */ + public function setRequestId($var) + { + GPBUtil::checkString($var, True); + $this->request_id = $var; + + return $this; + } + +} + diff --git a/app/Grpc/FlexibleResponse.php b/app/Grpc/FlexibleResponse.php new file mode 100644 index 0000000..3769c41 --- /dev/null +++ b/app/Grpc/FlexibleResponse.php @@ -0,0 +1,271 @@ +gateway.FlexibleResponse + */ +class FlexibleResponse extends \Google\Protobuf\Internal\Message +{ + /** + * Success indicator + * + * Generated from protobuf field bool success = 1; + */ + protected $success = false; + /** + * HTTP-like status code + * + * Generated from protobuf field int32 status_code = 2; + */ + protected $status_code = 0; + /** + * Generic response data - can be any protobuf message or raw data + * + * Generated from protobuf field .google.protobuf.Any data = 3; + */ + protected $data = null; + /** + * Error information (if any) + * + * Generated from protobuf field .gateway.ErrorInfo error = 4; + */ + protected $error = null; + /** + * Response metadata + * + * Generated from protobuf field map headers = 5; + */ + private $headers; + /** + * Request ID for tracing + * + * Generated from protobuf field string request_id = 6; + */ + protected $request_id = ''; + /** + * Response timestamp + * + * Generated from protobuf field .google.protobuf.Timestamp timestamp = 7; + */ + protected $timestamp = null; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @type bool $success + * Success indicator + * @type int $status_code + * HTTP-like status code + * @type \Google\Protobuf\Any $data + * Generic response data - can be any protobuf message or raw data + * @type \App\Grpc\ErrorInfo $error + * Error information (if any) + * @type array|\Google\Protobuf\Internal\MapField $headers + * Response metadata + * @type string $request_id + * Request ID for tracing + * @type \Google\Protobuf\Timestamp $timestamp + * Response timestamp + * } + */ + public function __construct($data = NULL) { + \App\Grpc\Gateway\Apigw::initOnce(); + parent::__construct($data); + } + + /** + * Success indicator + * + * Generated from protobuf field bool success = 1; + * @return bool + */ + public function getSuccess() + { + return $this->success; + } + + /** + * Success indicator + * + * Generated from protobuf field bool success = 1; + * @param bool $var + * @return $this + */ + public function setSuccess($var) + { + GPBUtil::checkBool($var); + $this->success = $var; + + return $this; + } + + /** + * HTTP-like status code + * + * Generated from protobuf field int32 status_code = 2; + * @return int + */ + public function getStatusCode() + { + return $this->status_code; + } + + /** + * HTTP-like status code + * + * Generated from protobuf field int32 status_code = 2; + * @param int $var + * @return $this + */ + public function setStatusCode($var) + { + GPBUtil::checkInt32($var); + $this->status_code = $var; + + return $this; + } + + /** + * Generic response data - can be any protobuf message or raw data + * + * Generated from protobuf field .google.protobuf.Any data = 3; + * @return \Google\Protobuf\Any + */ + public function getData() + { + return $this->data; + } + + /** + * Generic response data - can be any protobuf message or raw data + * + * Generated from protobuf field .google.protobuf.Any data = 3; + * @param \Google\Protobuf\Any $var + * @return $this + */ + public function setData($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Any::class); + $this->data = $var; + + return $this; + } + + /** + * Error information (if any) + * + * Generated from protobuf field .gateway.ErrorInfo error = 4; + * @return \App\Grpc\ErrorInfo + */ + public function getError() + { + return $this->error; + } + + /** + * Error information (if any) + * + * Generated from protobuf field .gateway.ErrorInfo error = 4; + * @param \App\Grpc\ErrorInfo $var + * @return $this + */ + public function setError($var) + { + GPBUtil::checkMessage($var, \App\Grpc\ErrorInfo::class); + $this->error = $var; + + return $this; + } + + /** + * Response metadata + * + * Generated from protobuf field map headers = 5; + * @return \Google\Protobuf\Internal\MapField + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Response metadata + * + * Generated from protobuf field map headers = 5; + * @param array|\Google\Protobuf\Internal\MapField $var + * @return $this + */ + public function setHeaders($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING); + $this->headers = $arr; + + return $this; + } + + /** + * Request ID for tracing + * + * Generated from protobuf field string request_id = 6; + * @return string + */ + public function getRequestId() + { + return $this->request_id; + } + + /** + * Request ID for tracing + * + * Generated from protobuf field string request_id = 6; + * @param string $var + * @return $this + */ + public function setRequestId($var) + { + GPBUtil::checkString($var, True); + $this->request_id = $var; + + return $this; + } + + /** + * Response timestamp + * + * Generated from protobuf field .google.protobuf.Timestamp timestamp = 7; + * @return \Google\Protobuf\Timestamp + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * Response timestamp + * + * Generated from protobuf field .google.protobuf.Timestamp timestamp = 7; + * @param \Google\Protobuf\Timestamp $var + * @return $this + */ + public function setTimestamp($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->timestamp = $var; + + return $this; + } + +} + diff --git a/app/Grpc/Gateway/Apigw.php b/app/Grpc/Gateway/Apigw.php new file mode 100644 index 0000000..e9912cc --- /dev/null +++ b/app/Grpc/Gateway/Apigw.php @@ -0,0 +1,26 @@ +internalAddGeneratedFile(hex2bin( + "0afc0d0a0b61706967772e70726f746f1207676174657761791a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f22d5010a0f466c657869626c6552657175657374120f0a0773657276696365180120012809120e0a066d6574686f6418022001280912250a077061796c6f616418032001280b32142e676f6f676c652e70726f746f6275662e416e7912360a076865616465727318042003280b32252e676174657761792e466c657869626c65526571756573742e48656164657273456e74727912120a0a726571756573745f69641805200128091a2e0a0c48656164657273456e747279120b0a036b6579180120012809120d0a0576616c75651802200128093a02380122ab020a10466c657869626c65526573706f6e7365120f0a077375636365737318012001280812130a0b7374617475735f636f646518022001280512220a046461746118032001280b32142e676f6f676c652e70726f746f6275662e416e7912210a056572726f7218042001280b32122e676174657761792e4572726f72496e666f12370a076865616465727318052003280b32262e676174657761792e466c657869626c65526573706f6e73652e48656164657273456e74727912120a0a726571756573745f6964180620012809122d0a0974696d657374616d7018072001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d701a2e0a0c48656164657273456e747279120b0a036b6579180120012809120d0a0576616c75651802200128093a02380122cf010a0c427974657352657175657374120f0a0773657276696365180120012809120e0a066d6574686f64180220012809120f0a077061796c6f616418032001280c12140a0c636f6e74656e745f7479706518042001280912330a076865616465727318052003280b32222e676174657761792e4279746573526571756573742e48656164657273456e74727912120a0a726571756573745f69641806200128091a2e0a0c48656164657273456e747279120b0a036b6579180120012809120d0a0576616c75651802200128093a02380122a5020a0d4279746573526573706f6e7365120f0a077375636365737318012001280812130a0b7374617475735f636f6465180220012805120c0a046461746118032001280c12140a0c636f6e74656e745f7479706518042001280912210a056572726f7218052001280b32122e676174657761792e4572726f72496e666f12340a076865616465727318062003280b32232e676174657761792e4279746573526573706f6e73652e48656164657273456e74727912120a0a726571756573745f6964180720012809122d0a0974696d657374616d7018082001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d701a2e0a0c48656164657273456e747279120b0a036b6579180120012809120d0a0576616c75651802200128093a02380122a0010a094572726f72496e666f120f0a076d657373616765180120012809120c0a04636f6465180220012809120f0a0764657461696c7318032003280912320a086d6574616461746118042003280b32202e676174657761792e4572726f72496e666f2e4d65746164617461456e7472791a2f0a0d4d65746164617461456e747279120b0a036b6579180120012809120d0a0576616c75651802200128093a02380122250a124865616c7468436865636b52657175657374120f0a0773657276696365180120012809228d010a134865616c7468436865636b526573706f6e7365123a0a0673746174757318012001280e322a2e676174657761792e4865616c7468436865636b526573706f6e73652e53657276696e67537461747573223a0a0d53657276696e67537461747573120b0a07554e4b4e4f574e1000120b0a0753455256494e471001120f0a0b4e4f545f53455256494e47100232eb010a0e476174657761795365727669636512440a0d48616e646c655265717565737412182e676174657761792e466c657869626c65526571756573741a192e676174657761792e466c657869626c65526573706f6e736512430a1248616e646c6542797465735265717565737412152e676174657761792e4279746573526571756573741a162e676174657761792e4279746573526573706f6e7365124e0a1348616e646c6553747265616d5265717565737412182e676174657761792e466c657869626c65526571756573741a192e676174657761792e466c657869626c65526573706f6e73652801300132530a0d4865616c74685365727669636512420a05436865636b121b2e676174657761792e4865616c7468436865636b526571756573741a1c2e676174657761792e4865616c7468436865636b526573706f6e7365421eca02084170705c47727063e202104170705c477270635c47617465776179620670726f746f33" + ), true); + + static::$is_initialized = true; + } +} + diff --git a/app/Grpc/GatewayServiceClient.php b/app/Grpc/GatewayServiceClient.php new file mode 100644 index 0000000..dc9f317 --- /dev/null +++ b/app/Grpc/GatewayServiceClient.php @@ -0,0 +1,62 @@ +_simpleRequest('/gateway.GatewayService/HandleRequest', + $argument, + ['\App\Grpc\FlexibleResponse', 'decode'], + $metadata, $options); + } + + /** + * Handle any request with raw bytes (maximum flexibility) + * @param \App\Grpc\BytesRequest $argument input argument + * @param array $metadata metadata + * @param array $options call options + * @return \App\Grpc\BytesResponse + */ + public function HandleBytesRequest(\App\Grpc\BytesRequest $argument, + $metadata = [], $options = []) { + return $this->_simpleRequest('/gateway.GatewayService/HandleBytesRequest', + $argument, + ['\App\Grpc\BytesResponse', 'decode'], + $metadata, $options); + } + + /** + * Streaming support for real-time data + * @param array $metadata metadata + * @param array $options call options + * @return \App\Grpc\FlexibleResponse + */ + public function HandleStreamRequest($metadata = [], $options = []) { + return $this->_bidiRequest('/gateway.GatewayService/HandleStreamRequest', + ['\App\Grpc\FlexibleResponse','decode'], + $metadata, $options); + } + +} diff --git a/app/Grpc/GrpcRoute.php b/app/Grpc/GrpcRoute.php new file mode 100644 index 0000000..55d6f70 --- /dev/null +++ b/app/Grpc/GrpcRoute.php @@ -0,0 +1,11 @@ + $controller, + 'action' => $action + ]; + } + + public static function resolve(string $service, string $method): ?array + { + return self::$routes[$service][$method] ?? null; + } + + public static function getAllRoutes(): array + { + return self::$routes; + } +} diff --git a/app/Grpc/GrpcServiceRouter.php b/app/Grpc/GrpcServiceRouter.php new file mode 100644 index 0000000..3de8200 --- /dev/null +++ b/app/Grpc/GrpcServiceRouter.php @@ -0,0 +1,19 @@ +serviceName = $serviceName; + } + + public function method(string $methodName, string $controller, string $action): self + { + GrpcRouteRegistry::register($this->serviceName, $methodName, $controller, $action); + return $this; + } +} diff --git a/app/Grpc/Services/GatewayService.php b/app/Grpc/Services/GatewayService.php new file mode 100644 index 0000000..ba8c4f3 --- /dev/null +++ b/app/Grpc/Services/GatewayService.php @@ -0,0 +1,222 @@ +grpcServices = config('services.microservices_grpc'); + } + + /** + * Handle flexible protobuf requests + */ + public function HandleClientRequest(FlexibleRequest $in): FlexibleResponse + { + $response = new FlexibleResponse(); + $response->setRequestId($in->getRequestId()); + $response->setTimestamp($this->getCurrentTimestamp()); + + // try { + // Get a gRPC client for the target service + $grpcClient = $this->getGrpcClient($in->getService()); + if (!$grpcClient) { + return $this->createErrorResponse($response, 404, 'SERVICE_NOT_FOUND', 'Service not found: ' . $in->getService()); + } + app(BaseGrpcController::class)->setSpecifiedService($in); + + // Forward the exact same FlexibleRequest to downstream service + // The downstream service implements the same flexible proto + $metadata = []; + if ($bearerToken = request()->bearerToken()) + $metadata['authorization'] = ['Bearer ' . $bearerToken]; + list($downstreamResponse, $status) = $grpcClient->HandleRequest($in, $metadata)->wait(); + if ($status->code === STATUS_OK) { + // Forward the response as-is + $response->setSuccess($downstreamResponse->getSuccess()); + $response->setStatusCode($downstreamResponse->getStatusCode()); + $response->setData($downstreamResponse->getData()); + + // Forward headers + foreach ($downstreamResponse->getHeaders() as $key => $value) { + $response->getHeaders()[$key] = $value; + } + + } else { + // dd($downstreamResponse->getError()); + if (is_null($downstreamResponse)) + return $this->createErrorResponse( + $response, + 500, + 'GRPC_ERROR', + 'Downstream service, code ' . $status->code . ', returned an error: ' . $status->details + ); + return $this->createErrorResponse( + $response, + $downstreamResponse->getStatusCode(), + 'GRPC_ERROR', + $downstreamResponse->getError() ? $downstreamResponse->getError()->getMessage() : 'Unknown error', + data: $downstreamResponse->getData() + ); + } + + // } catch (Exception $e) { + + // Log::error('Gateway gRPC request failed', [ + // 'service' => $in->getService(), + // 'method' => $in->getMethod(), + // 'error' => $e->getMessage() + // ]); + + // return $this->createErrorResponse($response, 500, 'INTERNAL_ERROR', $e->getMessage()); + // } + + return $response; + } + + public function HandleRequest(FlexibleRequest $request): FlexibleResponse + { + try { + // Extract service and method from the request + $serviceName = $request->getService(); // You'll need to add this to your FlexibleRequest + $methodName = $request->getMethod(); // You'll need to add this to your FlexibleRequest + + // Resolve the route + $route = GrpcRouteRegistry::resolve($serviceName, $methodName); + + if (!$route) { + throw new Exception("Route not found: {$serviceName}::{$methodName}"); + } + + // Instantiate controller + $controllerClass = "App\\Grpc\\Controllers\\{$route['controller']}"; + $controller = app($controllerClass); + + // Call the action + return $controller->{$route['action']}($request); + + } catch (Exception $e) { + Log::error("Failed to handle gRPC request", [$e->getMessage()]); + return new FlexibleResponse([ + 'status' => 'error', + 'code' => 500, + 'data' => json_encode(['message' => $e->getMessage()]) + ]); + } + } + + /** + * Get gRPC client for service + */ + private function getGrpcClient(string $service): ?GatewayServiceClient + { + if (str_contains($service, '/')) { + $main_sub_service = explode('/', $service); + $service = $main_sub_service[0]; + } + + if (!isset($this->grpcServices[$service])) { + return null; + } + + $serviceAddress = $this->grpcServices[$service]; + + try { + // Simplified options to avoid compatibility issues + $options = [ + 'credentials' => ChannelCredentials::createInsecure(), + 'timeout' => 30000000, // 30 seconds in microseconds + ]; + Log::info("Created gRPC client for service: {$service} at {$serviceAddress}"); + + return new GatewayServiceClient( + $serviceAddress, + $options + ); + + } catch (Exception $e) { + Log::error("Failed to create gRPC client for service: {$service}", [ + 'address' => $serviceAddress, + 'error' => $e->getMessage() + ]); + return null; + } + } + + /** + * Create error response for FlexibleResponse + */ + private function createErrorResponse(FlexibleResponse $response, int $statusCode, string $code, string $message, ?Any $data = null): FlexibleResponse + { + $error = new ErrorInfo(); + $error->setCode($code); + $error->setMessage($message); + + $response->setData($data); + $response->setSuccess(false); + $response->setStatusCode($statusCode); + $response->setError($error); + + return $response; + } + + /** + * Create error response for BytesResponse + */ + private function createBytesErrorResponse(BytesResponse $response, int $statusCode, string $code, string $message): BytesResponse + { + $error = new ErrorInfo(); + $error->setCode($code); + $error->setMessage($message); + + $response->setSuccess(false); + $response->setStatusCode($statusCode); + $response->setError($error); + $response->setContentType('application/json'); + $response->setData(json_encode(['error' => $code, 'message' => $message])); + + return $response; + } + + /** + * Get current timestamp + */ + private function getCurrentTimestamp(): Timestamp + { + $timestamp = new Timestamp(); + $timestamp->fromDateTime(new DateTime()); + return $timestamp; + } + + public function getMethodDescriptors(): array + { + return [ + '/gateway.GatewayService/HandleRequest' => new MethodDescriptor( + $this, + 'HandleRequest', + FlexibleRequest::class, + MethodDescriptor::UNARY_CALL, + ), + ]; + } + +} diff --git a/app/Grpc/Services/GrpcService.php b/app/Grpc/Services/GrpcService.php new file mode 100644 index 0000000..cc0ad76 --- /dev/null +++ b/app/Grpc/Services/GrpcService.php @@ -0,0 +1,64 @@ +setService($service); + $grpcRequest->setMethod($method); + $grpcRequest->setRequestId(uniqid()); + + // Pack request data as Any + $payload = new Any(); + $payload->setValue(json_encode($request->all())); + $grpcRequest->setPayload($payload); + + + // Call gRPC service + $grpcResponse = $this->gatewayService->HandleClientRequest($grpcRequest); + return $this->convertBackToHTTPResponse($grpcResponse); + + } + + public function forwardGrpcToService(FlexibleRequest $request): FlexibleResponse + { + return $this->gatewayService->HandleClientRequest($request); + } + + /** + * @param FlexibleResponse $grpcResponse + * @return JsonResponse + */ + public function convertBackToHTTPResponse(FlexibleResponse $grpcResponse): JsonResponse + { +// Convert back to HTTP response + if ($grpcResponse->getSuccess()) { + $data = json_decode($grpcResponse->getData()->getValue()) ?? $grpcResponse->getData()->getValue(); + return response()->json($data, $grpcResponse->getStatusCode()); + } else { + $error = $grpcResponse->getError()->getMessage(); + $data = $grpcResponse->getData() ? json_decode($grpcResponse->getData()->getValue()) : null; + if (data_get($data, 'message')) + $res['message'] = data_get($data, 'message'); + + $res['error'] = json_decode($error) ?? $error; + return response()->json($res, $grpcResponse->getStatusCode()); + } + } +} diff --git a/app/Http/Controllers/ApiGatewayController.php b/app/Http/Controllers/ApiGatewayController.php new file mode 100644 index 0000000..7ebec60 --- /dev/null +++ b/app/Http/Controllers/ApiGatewayController.php @@ -0,0 +1,76 @@ + $request->bearerToken() + ]); + + if ($res->failed()) + if ($res->getStatusCode() == Response::HTTP_NOT_FOUND) + return response()->json([ + 'message' => $res->json('details', 'No purchased products found'), + ], $res->getStatusCode()); + else + throw new ConnectionException('Failed to connect to AI API', $res->status()); + + $recommendedProductData = $res->json(); + $request->merge($this->productService->injectUserId()); + if (!$recommendedProductIds = array_column($recommendedProductData, 'id')) + return response()->json([ + 'message' => 'No recommended products found', + 'data' => [] + ], Response::HTTP_OK); + + $request->merge(['recommended_product_ids' => $recommendedProductIds]); + $res = $this->grpcService->createClientRequest( + $request, + 'ProductService', + 'listProducts', + ); + if ($res->getStatusCode() == Response::HTTP_OK) { + $filteredProductData = ($res->getData(true)); + $filteredProductData = data_get($filteredProductData, 'data'); + + foreach ($filteredProductData as $index => &$productData) { + $productData['percentage'] = $recommendedProductData[$index]['compatibility_score'] ?? 0; + $productData['percentage'] = $productData['percentage'] * 100; // Convert to percentage + $productData['percentage'] = number_format($productData['percentage'], 2) . '%'; + } + + $res->setData($filteredProductData); + return $this->response("Recommended products fetched successfully", $res->getData(true), Response::HTTP_OK); + } else + throw new ConnectionException($res->getData(true), $res->getStatusCode()); + } catch (ConnectionException $e) { + return response()->json([ + 'message' => 'Connection error', + 'error' => $e->getMessage() + ], 500); + } + } +} diff --git a/app/Http/Controllers/AuthApiController.php b/app/Http/Controllers/AuthApiController.php new file mode 100644 index 0000000..789ccc2 --- /dev/null +++ b/app/Http/Controllers/AuthApiController.php @@ -0,0 +1,80 @@ +service->login($request->all()); +// +// return $this->response($res['message'], $res['data'], $status); +// } catch (Exception $exception) { +// return $this->response($exception->getMessage(), status: $exception->getCode(), error: true); +// } +// } + public function login(Request $request): JsonResponse + { + try { +// $res = $this->userGrpcService->authenticateUser($request->email, $request->password); + return $this->grpcService->createClientRequest($request, + $this->serviceName, + 'Login' + ); +// return $this->response($message, $data, Response::HTTP_OK, error: !$success); + } catch (Exception $exception) { + return $this->response($exception->getMessage(), status: $exception->getCode(), error: true); + } + } + + /** + */ + public function signup(Request $request): Response + { + try { +// $res = $this->userGrpcService->createUser($request->all()); + return $this->grpcService->createClientRequest($request, + $this->serviceName, + 'SignUp' + ); + } catch (Exception $e) { + return $this->response($e->getMessage(), status: $e->getCode() ?: Response::HTTP_BAD_REQUEST, error: true); + } + } + + /** + */ + public + function logout(Request $request) + { + try { + return $this->grpcService->createClientRequest( + $request, + $this->serviceName, + 'Logout' + ); +// $success = $res['success']; +// $message = $res['message']; +// $data = $res['data']; +// return $this->response($message, $data, Response::HTTP_NO_CONTENT, error: !$success); + } catch (Exception $exception) { + return $this->response($exception->getMessage(), status: $exception->getCode(), error: true); + } + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php deleted file mode 100644 index d1a8aca..0000000 --- a/app/Http/Controllers/AuthController.php +++ /dev/null @@ -1,58 +0,0 @@ -service->login($request->all()); - - return $this->response($res['message'], $res['data'], $status); - } catch (Exception $exception) { - return $this->response($exception->getMessage(), status: $exception->getCode(), error: true); - } - } - - /** - */ - public function signup(Request $request): UserResource|JsonResponse - { - try { - $res = $this->service->create($request->all()); - - if ($res->status() == Response::HTTP_CREATED) - return $this->response($res->json('message'), $res->json('data'), $res->status()); - else - return $this->response($res->json('message'), $res->json('errors'), $res->status()); - } catch (Exception $e) { - return $this->response($e->getMessage(), status: $e->getCode() ?: Response::HTTP_BAD_REQUEST, error: true); - } - } - - /** - */ - public function logout(Request $request) - { - try { - [$res, $status] = $this->service->logout($request->bearerToken()); - return $this->response($res['message'], $res['data'], $status); - } catch (Exception $exception) { - return $this->response($exception->getMessage(), status: $exception->getCode(), error: true); - } - } -} diff --git a/app/Http/Controllers/GoogleController.php b/app/Http/Controllers/GoogleApiController.php similarity index 95% rename from app/Http/Controllers/GoogleController.php rename to app/Http/Controllers/GoogleApiController.php index 1c0a0c1..ec2da71 100644 --- a/app/Http/Controllers/GoogleController.php +++ b/app/Http/Controllers/GoogleApiController.php @@ -6,7 +6,7 @@ use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; -class GoogleController extends BaseController +class GoogleApiController extends BaseController { public function __construct( private UserService $userService, diff --git a/app/Http/Controllers/OfferController.php b/app/Http/Controllers/OfferApiController.php similarity index 63% rename from app/Http/Controllers/OfferController.php rename to app/Http/Controllers/OfferApiController.php index 30298a0..3699123 100644 --- a/app/Http/Controllers/OfferController.php +++ b/app/Http/Controllers/OfferApiController.php @@ -3,15 +3,18 @@ namespace App\Http\Controllers; use App\External_Apis\Services\OfferService; +use App\Grpc\Services\GrpcService; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Request; use Illuminate\Routing\Controllers\HasMiddleware; use Illuminate\Routing\Controllers\Middleware; use Symfony\Component\HttpFoundation\Response; -class OfferController extends BaseController implements HasMiddleware +class OfferApiController extends BaseController implements HasMiddleware { - public function __construct(public OfferService $offerService) + public function __construct(public OfferService $offerService, + private readonly GrpcService $grpcService + ) { } @@ -20,20 +23,26 @@ public function __construct(public OfferService $offerService) */ public function index(Request $request) { - try { - [$res, $status] = $this->offerService->index($request->all()); - if ($status != Response::HTTP_OK && isset($res['errors'])) - $mes = $res['errors']; - else - $mes = $res['message']; - - return $this->response($mes, $res['data'], $status, error: $status != Response::HTTP_OK); - } catch (\Exception $e) { - return $this->response($e->getMessage(), [ - 'file' => $e->getFile(), - 'line' => $e->getLine() - ], $e->getCode(), error: true); - } + $request->merge($this->offerService->injectUserId()); + return $this->grpcService->createClientRequest( + $request, + 'ProductService', + 'listOffers', + ); +// try { +// [$res, $status] = $this->offerService->index($request->all()); +// if ($status != Response::HTTP_OK && isset($res['errors'])) +// $mes = $res['errors']; +// else +// $mes = $res['message']; +// +// return $this->response($mes, $res['data'], $status, error: $status != Response::HTTP_OK); +// } catch (\Exception $e) { +// return $this->response($e->getMessage(), [ +// 'file' => $e->getFile(), +// 'line' => $e->getLine() +// ], $e->getCode(), error: true); +// } } /** @@ -42,13 +51,19 @@ public function index(Request $request) public function show($id) { try { - [$res, $status] = $this->offerService->show($id); - if ($status != Response::HTTP_OK && isset($res['errors'])) - $mes = $res['errors']; - else - $mes = $res['message']; + request()->merge(['id' => $id]); + return $this->grpcService->createClientRequest( + request(), + 'ProductService', + 'getOffer', + ); +// [$res, $status] = $this->offerService->show($id); +// if ($status != Response::HTTP_OK && isset($res['errors'])) +// $mes = $res['errors']; +// else +// $mes = $res['message']; - return $this->response($mes, $res['data'], $status, error: $status != Response::HTTP_OK); +// return $this->response($mes, $res['data'], $status, error: $status != Response::HTTP_OK); } catch (\Exception $e) { return $this->response($e->getMessage(), [$e->getFile(), $e->getLine()], $e->getCode(), error: true); } @@ -69,7 +84,7 @@ public function store(Request $request) error: $status != Response::HTTP_CREATED ); } catch (\Exception $e) { - return $this->response('service '.$e->getMessage(), + return $this->response('service ' . $e->getMessage(), [$e->getFile(), $e->getLine(), $e->getTrace() diff --git a/app/Http/Controllers/OrderApiController.php b/app/Http/Controllers/OrderApiController.php index d802d68..51931de 100644 --- a/app/Http/Controllers/OrderApiController.php +++ b/app/Http/Controllers/OrderApiController.php @@ -50,4 +50,15 @@ public function update(Request $request, $id) return $this->response($res->json('message'), $res->json('data'), status: $res->status()); return $this->response($res->json('error'), $res->json('data'), status: $res->status(), error: true); } + + /** + * @throws ConnectionException + */ + public function purchaseHistory() + { + $res = http::withToken(request()->bearerToken())->get(OrderApi::getBaseUrl() . 'api/purchase-history'); + if ($res->status() == Response::HTTP_OK) + return $this->response($res->json('message'), $res->json('data'), status: $res->status()); + return $this->response($res->json('error'), $res->json('data'), status: $res->status(), error: true); + } } diff --git a/app/Http/Controllers/PasswordController.php b/app/Http/Controllers/PasswordApiController.php similarity index 93% rename from app/Http/Controllers/PasswordController.php rename to app/Http/Controllers/PasswordApiController.php index 39b53c8..f8560da 100644 --- a/app/Http/Controllers/PasswordController.php +++ b/app/Http/Controllers/PasswordApiController.php @@ -6,7 +6,7 @@ use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; -class PasswordController extends BaseController +class PasswordApiController extends BaseController { public function __construct( private readonly UserService $userService @@ -18,8 +18,8 @@ public function sendResetLink(Request $request) { try { [$res, $status] = $this->userService->sendResetLink($request->all()); - if ($status != Response::HTTP_OK && isset($res['errors'])) - $mes = $res['errors']; + if ($status != Response::HTTP_OK && isset($res['error'])) + $mes = $res['error']; else $mes = $res['message']; diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductApiController.php similarity index 66% rename from app/Http/Controllers/ProductController.php rename to app/Http/Controllers/ProductApiController.php index 1796232..f7bfc5c 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductApiController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\External_Apis\Services\ProductService; +use App\Grpc\Services\GrpcService; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\RequestException; use Illuminate\Http\Request; @@ -11,10 +12,11 @@ use Illuminate\Support\Facades\Validator; use Symfony\Component\HttpFoundation\Response; -class ProductController extends BaseController implements HasMiddleware +class ProductApiController extends BaseController implements HasMiddleware { public function __construct( - private readonly ProductService $productService + private readonly ProductService $productService, + private readonly GrpcService $grpcService ) { @@ -32,28 +34,41 @@ public static function middleware() */ public function index(Request $request) { - [$res, $status] = $this->productService->index($request->all()); - $isError = $status != Response::HTTP_OK; - if ($isError && isset($res['errors'])) - $msg = $res['errors']; - else - $msg = $res['message']; - - return $this->response($msg, $res['data'], $status, error: $isError); + $request->merge($this->productService->injectUserId()); + return $this->grpcService->createClientRequest( + $request, + 'ProductService', + 'listProducts', + ); +// [$res, $status] = $this->productService->index($request->all()); +// $isError = $status != Response::HTTP_OK; +// if ($isError && isset($res['errors'])) +// $msg = $res['errors']; +// else +// $msg = $res['message']; +// +// return $this->response($msg, $res['data'], $status, error: $isError); } /** * @throws ConnectionException */ public function show(int $product_id) + { - [$res, $status] = $this->productService->show($product_id); - $isError = $status != Response::HTTP_OK; - if ($isError && isset($res['errors'])) - $msg = $res['errors']; - else - $msg = $res['message']; - return $this->response($msg, $res['data'], $status, error: $isError); + request()->merge(['id' => $product_id]); + return $this->grpcService->createClientRequest( + request(), + 'ProductService', + 'getProduct', + ); +// [$res, $status] = $this->productService->show($product_id); +// $isError = $status != Response::HTTP_OK; +// if ($isError && isset($res['errors'])) +// $msg = $res['errors']; +// else +// $msg = $res['message']; +// return $this->response($msg, $res['data'], $status, error: $isError); } /** diff --git a/app/Http/Controllers/ShippingAddressController.php b/app/Http/Controllers/ShippingAddressApiController.php similarity index 98% rename from app/Http/Controllers/ShippingAddressController.php rename to app/Http/Controllers/ShippingAddressApiController.php index 94ae6fb..9e87a69 100644 --- a/app/Http/Controllers/ShippingAddressController.php +++ b/app/Http/Controllers/ShippingAddressApiController.php @@ -7,7 +7,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Symfony\Component\HttpFoundation\Response; -class ShippingAddressController extends BaseController +class ShippingAddressApiController extends BaseController { /** * Display a listing of the resource. diff --git a/app/Http/Controllers/ShoppingCartApiController.php b/app/Http/Controllers/ShoppingCartApiController.php new file mode 100644 index 0000000..62b8cca --- /dev/null +++ b/app/Http/Controllers/ShoppingCartApiController.php @@ -0,0 +1,76 @@ +grpcService->createClientRequest( + $request, + $this->service, + 'GetCart', + ); +// $res = http::withToken(request()->bearerToken())->get(UserApi::getBaseUrl() . 'api/carts'); +// return $this->response($res->json('message'), $res->json('data'), +// otherData: ["total" => $res->json('total')]); + } + + /** + */ + public function add(Request $request) + { + return $this->grpcService->createClientRequest( + $request, + $this->service, + 'AddToCart', + ); +// $res = http::withToken(request()->bearerToken())->post(UserApi::getBaseUrl() . "api/carts", $request->all()); +// if ($res->status() == Response::HTTP_CREATED) +// return $this->response($res->json('message'), $res->json('data'), status: $res->status()); +// return $this->response($res->json('error'), $res->json('data'), status: $res->status()); + } + + /** + */ + public function remove($item_type, $item_id) + { + \request()->merge(['item_type' => $item_type, 'item_id' => $item_id]); + return $this->grpcService->createClientRequest( + request(), + $this->service, + 'RemoveFromCart' + ); +// $res = http::withToken(request()->bearerToken())->delete(UserApi::getBaseUrl() . "api/carts/$item_type/$item_id"); +// return $this->response($res->json('message'), $res->json('data')); + } + + /** + */ + public function clear() + { + return $this->grpcService->createClientRequest( + request(), + $this->service, + 'ClearCart' + ); +// $res = http::withToken(request()->bearerToken())->delete(UserApi::getBaseUrl() . "api/carts"); +// return $this->response($res->json('message'), $res->json('data')); + } +} diff --git a/app/Http/Controllers/ShoppingCartController.php b/app/Http/Controllers/ShoppingCartController.php deleted file mode 100644 index 9c0a8be..0000000 --- a/app/Http/Controllers/ShoppingCartController.php +++ /dev/null @@ -1,52 +0,0 @@ -bearerToken())->get(UserApi::getBaseUrl() . 'api/carts'); - return $this->response($res->json('message'), $res->json('data'), - otherData: ["total"=>$res->json('total')]); - } - - /** - * @throws ConnectionException - */ - public function add(Request $request) - { - $res = http::withToken(request()->bearerToken())->post(UserApi::getBaseUrl() . "api/carts", $request->all()); - if ($res->status() == Response::HTTP_CREATED) - return $this->response($res->json('message'), $res->json('data'), status: $res->status()); - return $this->response($res->json('error'), $res->json('data'), status: $res->status()); - } - - /** - * @throws ConnectionException - */ - public function remove($item_type, $item_id) - { - $res = http::withToken(request()->bearerToken())->delete(UserApi::getBaseUrl() . "api/carts/$item_type/$item_id"); - return $this->response($res->json('message'), $res->json('data')); - } - - /** - * @throws ConnectionException - */ - public function clear() - { - $res = http::withToken(request()->bearerToken())->delete(UserApi::getBaseUrl() . "api/carts"); - return $this->response($res->json('message'), $res->json('data')); - } -} diff --git a/app/Http/Controllers/UserApiController.php b/app/Http/Controllers/UserApiController.php index 9182944..36e51b5 100644 --- a/app/Http/Controllers/UserApiController.php +++ b/app/Http/Controllers/UserApiController.php @@ -2,45 +2,75 @@ namespace App\Http\Controllers; -use App\External_Apis\Services\UserService; +use App\Grpc\Services\GrpcService; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; -use Symfony\Component\HttpFoundation\Response; class UserApiController extends BaseController { + private string $serviceName='UserService'; + public function __construct( - private readonly UserService $userService + private readonly GrpcService $grpcService, + ) { } +// +// /** +// * Display a listing of the resource. +// * @throws ConnectionException +// */ +// public function index(Request $request) +// { +// [$res, $status] = $this->userService->index($request, $request->bearerToken()); +// return $this->response($res['message'], $res['data'], $status, error: $status != Response::HTTP_OK); +// } + /** * Display a listing of the resource. * @throws ConnectionException */ public function index(Request $request) { - [$res, $status] = $this->userService->index($request, $request->bearerToken()); - return $this->response($res['message'], $res['data'], $status, error: $status != Response::HTTP_OK); +// $res = $this->userGrpcService->listUsers(); + return $this->grpcService->createClientRequest(request(), + $this->serviceName, + 'ListUsers', + ); } /** * Display the specified resource. - * @throws ConnectionException */ - public function show($user_id) +// public function show($user_id) +// { +// Validator::validate( +// ['user_id' => $user_id], +// ['user_id' => 'required|integer'] +// ); +// +// [$res, $status] = $this->userService->show($user_id, request()->bearerToken()); +// +// return $this->response($res['message'], $res['data'], $status, error: $status != Response::HTTP_OK); +// } + + public function show($id) { Validator::validate( - ['user_id' => $user_id], + ['user_id' => $id], ['user_id' => 'required|integer'] ); - [$res, $status] = $this->userService->show($user_id, request()->bearerToken()); + request()->merge(['id' => $id]); + return $this->grpcService->createClientRequest(request(), + $this->serviceName, + 'GetUser', + ); - return $this->response($res['message'], $res['data'], $status, error: $status != Response::HTTP_OK); } /** @@ -59,15 +89,25 @@ public function destroy(string $id) // } +// /** +// * @throws ConnectionException +// */ +// public function userProfile(Request $request) +// { +// $res = $this->userService->userProfile($request->all()); +// if ($res['status'] == Response::HTTP_OK) +// return $this->response($res['message'], $res['data'], $res['status']); +// else +// return $this->response($res['message'], status: $res['status'], error: true); +// } /** - * @throws ConnectionException */ public function userProfile(Request $request) { - $res = $this->userService->userProfile($request->all()); - if ($res['status'] == Response::HTTP_OK) - return $this->response($res['message'], $res['data'], $res['status']); - else - return $this->response($res['message'], status: $res['status'], error: true); + return $this->grpcService->createClientRequest($request, + $this->serviceName, + 'GetUserProfile' + ); +// return $this->response($message, $data, Response::HTTP_OK, error: !$success); } } diff --git a/app/Http/Controllers/WishListController.php b/app/Http/Controllers/WishListApiController.php similarity index 78% rename from app/Http/Controllers/WishListController.php rename to app/Http/Controllers/WishListApiController.php index 9a6ae39..efd5853 100644 --- a/app/Http/Controllers/WishListController.php +++ b/app/Http/Controllers/WishListApiController.php @@ -6,7 +6,7 @@ use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Facades\Http; -class WishListController extends BaseController +class WishListApiController extends BaseController { /** * Display a listing of the resource. @@ -40,4 +40,13 @@ public function destroy(string $item_type, int $item_id) $res = http::withToken(request()->bearerToken())->delete(UserApi::getBaseUrl() . "api/wishlists/$item_type/$item_id"); return $this->response($res->json('message') ?? $res->json('error'), $res->json('errors'), status: $res->status()); } + + /** + * @throws ConnectionException + */ + public function clear() + { + $res = http::withToken(request()->bearerToken())->delete(UserApi::getBaseUrl() . 'api/wishlists/clear'); + return $this->response($res->json('message') ?? $res->json('error'), $res->json('errors'), status: $res->status()); + } } diff --git a/app/Http/Middleware/AuthenticateMiddleware.php b/app/Http/Middleware/AuthenticateMiddleware.php index bf72614..1ba3d38 100644 --- a/app/Http/Middleware/AuthenticateMiddleware.php +++ b/app/Http/Middleware/AuthenticateMiddleware.php @@ -8,11 +8,16 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Http; use Symfony\Component\HttpFoundation\Response; class AuthenticateMiddleware { + public function __construct( + private readonly \App\Grpc\Services\GrpcService $grpcService, + ) + { + } + /** * Handle an incoming request. * @@ -23,13 +28,17 @@ public function handle(Request $request, Closure $next): Response $token = $request->bearerToken(); $cacheKey = 'user_' . hash('sha256', $token); try { - $userModel = Cache::remember($cacheKey, 60 * 60 * 24, function () use ($token) { - $response = Http::withToken($token)->post(app('users')::getTokenApi()); - if ($response->failed()) - return null; - - return new User(json_decode($response->json('data'), true)); - }); + $userModel = Cache::remember($cacheKey, 60 * 60 * 24, function () use ($request, $token) { + $response = $this->grpcService->createClientRequest( + $request, + 'UserService/Auth', + 'ValidateToken', + ); + if ($response->getStatusCode() != Response::HTTP_OK) + return null; + + return new User(json_decode(data_get($response->getData(), 'data'), true)); + }); if (!$userModel) { Cache::forget($cacheKey); diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php deleted file mode 100644 index afb4db9..0000000 --- a/app/Http/Resources/UserResource.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'username' => $this->username, - 'email' => $this->whenNotNull($this->email), - 'token' => $this->token, - ]; - } -} diff --git a/app/Providers/GrpcServiceProvider.php b/app/Providers/GrpcServiceProvider.php new file mode 100644 index 0000000..521b147 --- /dev/null +++ b/app/Providers/GrpcServiceProvider.php @@ -0,0 +1,20 @@ +loadGrpcRoutes(); + } + + protected function loadGrpcRoutes(): void + { + if (file_exists(base_path('routes/grpc.php'))) { + require base_path('routes/grpc.php'); + } + } +} diff --git a/app/Traits/InjectUser.php b/app/Traits/InjectUser.php index b7875ab..98ed12d 100644 --- a/app/Traits/InjectUser.php +++ b/app/Traits/InjectUser.php @@ -4,9 +4,14 @@ trait InjectUser { - private function injectUserId(&$data=[]): void + /** + * @throws \Exception + */ + public function injectUserId(): array { - $data['user_id'] = auth()->user()->id; + if (is_null(auth()->user())) + throw new \Exception('User not authenticated'); + return ['user_id' => auth()->user()->id]; } } diff --git a/bootstrap/providers.php b/bootstrap/providers.php index a693850..a060716 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,4 +3,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\ExternalApiServiceProvider::class, + App\Providers\GrpcServiceProvider::class, ]; diff --git a/composer.json b/composer.json index 33a7091..58eab8b 100644 --- a/composer.json +++ b/composer.json @@ -8,8 +8,10 @@ "require": { "php": "^8.4", "laravel/framework": "^12.0", - "laravel/octane": "^2.8", - "laravel/tinker": "^2.10.1" + "laravel/tinker": "^2.10.1", + "grpc/grpc": "^1.57", + "google/protobuf": "^4.31", + "ext-grpc": "*" }, "require-dev": { "fakerphp/faker": "^1.24", diff --git a/composer.lock b/composer.lock index 95f8579..569419e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df1def02b3fd57624f7afc0a1ad360d8", + "content-hash": "48d551fc576552ae83ec6195033a4f26", "packages": [ { "name": "brick/math", - "version": "0.12.3", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "shasum": "" }, "require": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.13.1" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2025-03-29T13:50:30+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -581,6 +581,50 @@ ], "time": "2023-10-12T05:21:21+00:00" }, + { + "name": "google/protobuf", + "version": "v4.31.1", + "source": { + "type": "git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", + "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=5.0.0" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" + }, + "time": "2025-05-28T18:52:35+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.1.3", @@ -643,6 +687,50 @@ ], "time": "2024-07-20T21:45:45+00:00" }, + { + "name": "grpc/grpc", + "version": "1.57.0", + "source": { + "type": "git", + "url": "https://github.com/grpc/grpc-php.git", + "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grpc/grpc-php/zipball/b610c42022ed3a22f831439cb93802f2a4502fdf", + "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "google/auth": "^v1.3.0" + }, + "suggest": { + "ext-protobuf": "For better performance, install the protobuf C extension.", + "google/protobuf": "To get started using grpc quickly, install the native protobuf library." + }, + "type": "library", + "autoload": { + "psr-4": { + "Grpc\\": "src/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "gRPC library for PHP", + "homepage": "https://grpc.io", + "keywords": [ + "rpc" + ], + "support": { + "source": "https://github.com/grpc/grpc-php/tree/v1.57.0" + }, + "time": "2023-08-14T23:57:54+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.9.3", @@ -1054,110 +1142,22 @@ ], "time": "2025-02-03T10:55:03+00:00" }, - { - "name": "laminas/laminas-diactoros", - "version": "3.5.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "143a16306602ce56b8b092a7914fef03c37f9ed2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/143a16306602ce56b8b092a7914fef03c37f9ed2", - "reference": "143a16306602ce56b8b092a7914fef03c37f9ed2", - "shasum": "" - }, - "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", - "psr/http-factory": "^1.1", - "psr/http-message": "^1.1 || ^2.0" - }, - "conflict": { - "amphp/amp": "<2.6.4" - }, - "provide": { - "psr/http-factory-implementation": "^1.0", - "psr/http-message-implementation": "^1.1 || ^2.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^2.2.0", - "laminas/laminas-coding-standard": "~2.5.0", - "php-http/psr7-integration-tests": "^1.4.0", - "phpunit/phpunit": "^10.5.36", - "psalm/plugin-phpunit": "^0.19.0", - "vimeo/psalm": "^5.26.1" - }, - "type": "library", - "extra": { - "laminas": { - "module": "Laminas\\Diactoros", - "config-provider": "Laminas\\Diactoros\\ConfigProvider" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2024-10-14T11:59:49+00:00" - }, { "name": "laravel/framework", - "version": "v12.10.2", + "version": "v12.18.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "0f123cc857bc177abe4d417448d4f7164f71802a" + "reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/0f123cc857bc177abe4d417448d4f7164f71802a", - "reference": "0f123cc857bc177abe4d417448d4f7164f71802a", + "url": "https://api.github.com/repos/laravel/framework/zipball/7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d", + "reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d", "shasum": "" }, "require": { - "brick/math": "^0.11|^0.12", + "brick/math": "^0.11|^0.12|^0.13", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -1174,7 +1174,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", @@ -1266,7 +1266,7 @@ "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", - "predis/predis": "^2.3", + "predis/predis": "^2.3|^3.0", "resend/resend-php": "^0.10.0", "symfony/cache": "^7.2.0", "symfony/http-client": "^7.2.0", @@ -1298,7 +1298,7 @@ "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.3).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", @@ -1355,97 +1355,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-04-24T14:11:20+00:00" - }, - { - "name": "laravel/octane", - "version": "v2.9.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/octane.git", - "reference": "445002b2551c837d60cda4324259063c356dfb56" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/octane/zipball/445002b2551c837d60cda4324259063c356dfb56", - "reference": "445002b2551c837d60cda4324259063c356dfb56", - "shasum": "" - }, - "require": { - "laminas/laminas-diactoros": "^3.0", - "laravel/framework": "^10.10.1|^11.0|^12.0", - "laravel/prompts": "^0.1.24|^0.2.0|^0.3.0", - "laravel/serializable-closure": "^1.3|^2.0", - "nesbot/carbon": "^2.66.0|^3.0", - "php": "^8.1.0", - "symfony/console": "^6.0|^7.0", - "symfony/psr-http-message-bridge": "^2.2.0|^6.4|^7.0" - }, - "conflict": { - "spiral/roadrunner": "<2023.1.0", - "spiral/roadrunner-cli": "<2.6.0", - "spiral/roadrunner-http": "<3.3.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^7.6.1", - "inertiajs/inertia-laravel": "^1.3.2|^2.0", - "laravel/scout": "^10.2.1", - "laravel/socialite": "^5.6.1", - "livewire/livewire": "^2.12.3|^3.0", - "mockery/mockery": "^1.5.1", - "nunomaduro/collision": "^6.4.0|^7.5.2|^8.0", - "orchestra/testbench": "^8.21|^9.0|^10.0", - "phpstan/phpstan": "^2.1.7", - "phpunit/phpunit": "^10.4|^11.5", - "spiral/roadrunner-cli": "^2.6.0", - "spiral/roadrunner-http": "^3.3.0" - }, - "bin": [ - "bin/roadrunner-worker", - "bin/swoole-server" - ], - "type": "library", - "extra": { - "laravel": { - "aliases": { - "Octane": "Laravel\\Octane\\Facades\\Octane" - }, - "providers": [ - "Laravel\\Octane\\OctaneServiceProvider" - ] - }, - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\Octane\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Supercharge your Laravel application's performance.", - "keywords": [ - "frankenphp", - "laravel", - "octane", - "roadrunner", - "swoole" - ], - "support": { - "issues": "https://github.com/laravel/octane/issues", - "source": "https://github.com/laravel/octane" - }, - "time": "2025-04-13T21:10:35+00:00" + "time": "2025-06-10T14:48:34+00:00" }, { "name": "laravel/prompts", @@ -1635,16 +1545,16 @@ }, { "name": "league/commonmark", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94" + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/06c3b0bf2540338094575612f4a1778d0d2d5e94", - "reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "shasum": "" }, "require": { @@ -1681,7 +1591,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -1738,7 +1648,7 @@ "type": "tidelift" } ], - "time": "2025-04-18T21:09:27+00:00" + "time": "2025-05-05T12:20:28+00:00" }, { "name": "league/config", @@ -2289,16 +2199,16 @@ }, { "name": "nesbot/carbon", - "version": "3.9.0", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "6d16a8a015166fe54e22c042e0805c5363aef50d" + "reference": "ced71f79398ece168e24f7f7710462f462310d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6d16a8a015166fe54e22c042e0805c5363aef50d", - "reference": "6d16a8a015166fe54e22c042e0805c5363aef50d", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", + "reference": "ced71f79398ece168e24f7f7710462f462310d4d", "shasum": "" }, "require": { @@ -2391,7 +2301,7 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:57:33+00:00" + "time": "2025-05-01T19:51:51+00:00" }, { "name": "nette/schema", @@ -2457,16 +2367,16 @@ }, { "name": "nette/utils", - "version": "v4.0.6", + "version": "v4.0.7", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "ce708655043c7050eb050df361c5e313cf708309" + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309", - "reference": "ce708655043c7050eb050df361c5e313cf708309", + "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", "shasum": "" }, "require": { @@ -2537,22 +2447,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.6" + "source": "https://github.com/nette/utils/tree/v4.0.7" }, - "time": "2025-03-30T21:06:30+00:00" + "time": "2025-06-03T04:55:08+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -2595,37 +2505,37 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.8" + "symfony/console": "^7.2.6" }, "require-dev": { - "illuminate/console": "^11.33.2", - "laravel/pint": "^1.18.2", + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.11", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.8", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -2668,7 +2578,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" }, "funding": [ { @@ -2684,7 +2594,7 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2025-05-08T08:14:37+00:00" }, { "name": "phpoption/phpoption", @@ -3374,20 +3284,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.8.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -3396,26 +3306,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -3450,23 +3357,13 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.8.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-06-01T06:28:46+00:00" }, { "name": "symfony/clock", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", @@ -3520,7 +3417,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.2.0" + "source": "https://github.com/symfony/clock/tree/v7.3.0" }, "funding": [ { @@ -3540,23 +3437,24 @@ }, { "name": "symfony/console", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e51498ea18570c062e7df29d05a7003585b19b88" + "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", - "reference": "e51498ea18570c062e7df29d05a7003585b19b88", + "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", + "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^7.2" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -3613,7 +3511,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.5" + "source": "https://github.com/symfony/console/tree/v7.3.0" }, "funding": [ { @@ -3629,11 +3527,11 @@ "type": "tidelift" } ], - "time": "2025-03-12T08:11:12+00:00" + "time": "2025-05-24T10:34:04+00:00" }, { "name": "symfony/css-selector", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -3678,7 +3576,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.2.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.0" }, "funding": [ { @@ -3698,16 +3596,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -3720,7 +3618,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3745,7 +3643,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -3761,20 +3659,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/error-handler", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" + "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", - "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", + "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", "shasum": "" }, "require": { @@ -3787,9 +3685,11 @@ "symfony/http-kernel": "<6.4" }, "require-dev": { + "symfony/console": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -3820,7 +3720,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.5" + "source": "https://github.com/symfony/error-handler/tree/v7.3.0" }, "funding": [ { @@ -3836,20 +3736,20 @@ "type": "tidelift" } ], - "time": "2025-03-03T07:12:39+00:00" + "time": "2025-05-29T07:19:49+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", "shasum": "" }, "require": { @@ -3900,7 +3800,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" }, "funding": [ { @@ -3916,20 +3816,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-04-22T09:11:45+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -3943,7 +3843,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3976,7 +3876,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -3992,20 +3892,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", "shasum": "" }, "require": { @@ -4040,7 +3940,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v7.3.0" }, "funding": [ { @@ -4056,20 +3956,20 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2024-12-30T19:00:26+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "371272aeb6286f8135e028ca535f8e4d6f114126" + "reference": "4236baf01609667d53b20371486228231eb135fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126", - "reference": "371272aeb6286f8135e028ca535f8e4d6f114126", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4236baf01609667d53b20371486228231eb135fd", + "reference": "4236baf01609667d53b20371486228231eb135fd", "shasum": "" }, "require": { @@ -4086,6 +3986,7 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -4118,7 +4019,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.0" }, "funding": [ { @@ -4134,20 +4035,20 @@ "type": "tidelift" } ], - "time": "2025-03-25T15:54:33+00:00" + "time": "2025-05-12T14:48:23+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54" + "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54", - "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", "shasum": "" }, "require": { @@ -4155,8 +4056,8 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -4232,7 +4133,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.0" }, "funding": [ { @@ -4248,20 +4149,20 @@ "type": "tidelift" } ], - "time": "2025-03-28T13:32:50+00:00" + "time": "2025-05-29T07:47:32+00:00" }, { "name": "symfony/mailer", - "version": "v7.2.3", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3" + "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f3871b182c44997cf039f3b462af4a48fb85f9d3", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3", + "url": "https://api.github.com/repos/symfony/mailer/zipball/0f375bbbde96ae8c78e4aa3e63aabd486e33364c", + "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c", "shasum": "" }, "require": { @@ -4312,7 +4213,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.2.3" + "source": "https://github.com/symfony/mailer/tree/v7.3.0" }, "funding": [ { @@ -4328,20 +4229,20 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-04T09:51:09+00:00" }, { "name": "symfony/mime", - "version": "v7.2.4", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "87ca22046b78c3feaff04b337f33b38510fd686b" + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b", - "reference": "87ca22046b78c3feaff04b337f33b38510fd686b", + "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", "shasum": "" }, "require": { @@ -4396,7 +4297,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.4" + "source": "https://github.com/symfony/mime/tree/v7.3.0" }, "funding": [ { @@ -4412,11 +4313,11 @@ "type": "tidelift" } ], - "time": "2025-02-19T08:51:20+00:00" + "time": "2025-02-19T08:51:26+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4475,7 +4376,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -4495,7 +4396,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -4553,7 +4454,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -4573,16 +4474,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -4636,7 +4537,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -4652,11 +4553,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -4717,7 +4618,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -4737,19 +4638,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -4797,7 +4699,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -4813,20 +4715,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -4877,7 +4779,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -4893,11 +4795,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -4953,7 +4855,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" }, "funding": [ { @@ -4973,7 +4875,7 @@ }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -5032,7 +4934,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" }, "funding": [ { @@ -5052,16 +4954,16 @@ }, { "name": "symfony/process", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", "shasum": "" }, "require": { @@ -5093,90 +4995,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-03-13T12:21:46+00:00" - }, - { - "name": "symfony/psr-http-message-bridge", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", - "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/http-message": "^1.0|^2.0", - "symfony/http-foundation": "^6.4|^7.0" - }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-kernel": "<6.4" - }, - "require-dev": { - "nyholm/psr7": "^1.1", - "php-http/discovery": "^1.15", - "psr/log": "^1.1.4|^2|^3", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0" - }, - "type": "symfony-bridge", - "autoload": { - "psr-4": { - "Symfony\\Bridge\\PsrHttpMessage\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "PSR HTTP message bridge", - "homepage": "https://symfony.com", - "keywords": [ - "http", - "http-message", - "psr-17", - "psr-7" - ], - "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.2.0" + "source": "https://github.com/symfony/process/tree/v7.3.0" }, "funding": [ { @@ -5192,20 +5011,20 @@ "type": "tidelift" } ], - "time": "2024-09-26T08:57:56+00:00" + "time": "2025-04-17T09:11:12+00:00" }, { "name": "symfony/routing", - "version": "v7.2.3", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996" + "reference": "8e213820c5fea844ecea29203d2a308019007c15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ee9a67edc6baa33e5fae662f94f91fd262930996", - "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996", + "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", + "reference": "8e213820c5fea844ecea29203d2a308019007c15", "shasum": "" }, "require": { @@ -5257,7 +5076,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.2.3" + "source": "https://github.com/symfony/routing/tree/v7.3.0" }, "funding": [ { @@ -5273,20 +5092,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T10:56:55+00:00" + "time": "2025-05-24T20:43:28+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -5304,7 +5123,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5340,7 +5159,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -5356,20 +5175,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", "shasum": "" }, "require": { @@ -5427,7 +5246,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.3.0" }, "funding": [ { @@ -5443,20 +5262,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-20T20:19:01+00:00" }, { "name": "symfony/translation", - "version": "v7.2.4", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "283856e6981286cc0d800b53bd5703e8e363f05a" + "reference": "4aba29076a29a3aa667e09b791e5f868973a8667" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a", - "reference": "283856e6981286cc0d800b53bd5703e8e363f05a", + "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", + "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", "shasum": "" }, "require": { @@ -5466,6 +5285,7 @@ "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { + "nikic/php-parser": "<5.0", "symfony/config": "<6.4", "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", @@ -5479,7 +5299,7 @@ "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", @@ -5522,7 +5342,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.4" + "source": "https://github.com/symfony/translation/tree/v7.3.0" }, "funding": [ { @@ -5538,20 +5358,20 @@ "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-05-29T07:19:49+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -5564,7 +5384,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5600,7 +5420,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -5616,20 +5436,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/uid", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2d294d0c48df244c71c105a169d0190bfb080426" + "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", - "reference": "2d294d0c48df244c71c105a169d0190bfb080426", + "url": "https://api.github.com/repos/symfony/uid/zipball/7beeb2b885cd584cd01e126c5777206ae4c3c6a3", + "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3", "shasum": "" }, "require": { @@ -5674,7 +5494,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.2.0" + "source": "https://github.com/symfony/uid/tree/v7.3.0" }, "funding": [ { @@ -5690,24 +5510,25 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-05-24T14:28:13+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.2.3", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -5757,7 +5578,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" }, "funding": [ { @@ -5773,7 +5594,7 @@ "type": "tidelift" } ], - "time": "2025-01-17T11:39:41+00:00" + "time": "2025-04-27T18:39:23+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -5832,16 +5653,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -5900,7 +5721,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -5912,7 +5733,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { "name": "voku/portable-ascii", @@ -6113,16 +5934,16 @@ }, { "name": "filp/whoops", - "version": "2.18.0", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e" + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", - "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", + "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3", "shasum": "" }, "require": { @@ -6172,7 +5993,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.0" + "source": "https://github.com/filp/whoops/tree/2.18.2" }, "funding": [ { @@ -6180,24 +6001,24 @@ "type": "github" } ], - "time": "2025-03-15T12:00:00+00:00" + "time": "2025-06-11T20:42:19+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -6205,8 +6026,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -6229,22 +6050,22 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "laravel/pail", - "version": "v1.2.2", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "f31f4980f52be17c4667f3eafe034e6826787db2" + "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/f31f4980f52be17c4667f3eafe034e6826787db2", - "reference": "f31f4980f52be17c4667f3eafe034e6826787db2", + "url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a", + "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a", "shasum": "" }, "require": { @@ -6264,7 +6085,7 @@ "orchestra/testbench-core": "^8.13|^9.0|^10.0", "pestphp/pest": "^2.20|^3.0", "pestphp/pest-plugin-type-coverage": "^2.3|^3.0", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^1.12.27", "symfony/var-dumper": "^6.3|^7.0" }, "type": "library", @@ -6300,6 +6121,7 @@ "description": "Easily delve into your Laravel application's log files directly from the command line.", "homepage": "https://github.com/laravel/pail", "keywords": [ + "dev", "laravel", "logs", "php", @@ -6309,20 +6131,20 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2025-01-28T15:15:15+00:00" + "time": "2025-06-05T13:55:57+00:00" }, { "name": "laravel/pint", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36" + "reference": "941d1927c5ca420c22710e98420287169c7bcaf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", - "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", + "url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7", + "reference": "941d1927c5ca420c22710e98420287169c7bcaf7", "shasum": "" }, "require": { @@ -6334,11 +6156,11 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.75.0", - "illuminate/view": "^11.44.2", - "larastan/larastan": "^3.3.1", + "illuminate/view": "^11.44.7", + "larastan/larastan": "^3.4.0", "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3", + "nunomaduro/termwind": "^2.3.1", "pestphp/pest": "^2.36.0" }, "bin": [ @@ -6375,20 +6197,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-04-08T22:11:45+00:00" + "time": "2025-05-08T08:38:12+00:00" }, { "name": "laravel/sail", - "version": "v1.41.1", + "version": "v1.43.1", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "e5692510f1ef8e0f5096cde2b885d558f8d86592" + "reference": "3e7d899232a8c5e3ea4fc6dee7525ad583887e72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/e5692510f1ef8e0f5096cde2b885d558f8d86592", - "reference": "e5692510f1ef8e0f5096cde2b885d558f8d86592", + "url": "https://api.github.com/repos/laravel/sail/zipball/3e7d899232a8c5e3ea4fc6dee7525ad583887e72", + "reference": "3e7d899232a8c5e3ea4fc6dee7525ad583887e72", "shasum": "" }, "require": { @@ -6438,7 +6260,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-04-22T13:39:39+00:00" + "time": "2025-05-19T13:19:21+00:00" }, { "name": "mockery/mockery", @@ -6525,16 +6347,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -6573,7 +6395,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -6581,27 +6403,27 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.8.0", + "version": "v8.8.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8" + "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8", - "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5", + "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5", "shasum": "" }, "require": { - "filp/whoops": "^2.18.0", - "nunomaduro/termwind": "^2.3.0", + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", "php": "^8.2.0", - "symfony/console": "^7.2.5" + "symfony/console": "^7.3.0" }, "conflict": { "laravel/framework": "<11.44.2 || >=13.0.0", @@ -6609,15 +6431,15 @@ }, "require-dev": { "brianium/paratest": "^7.8.3", - "larastan/larastan": "^3.2", - "laravel/framework": "^11.44.2 || ^12.6", - "laravel/pint": "^1.21.2", - "laravel/sail": "^1.41.0", - "laravel/sanctum": "^4.0.8", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", "laravel/tinker": "^2.10.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", - "pestphp/pest": "^3.8.0", - "sebastian/environment": "^7.2.0 || ^8.0" + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2", + "sebastian/environment": "^7.2.1 || ^8.0" }, "type": "library", "extra": { @@ -6680,7 +6502,7 @@ "type": "patreon" } ], - "time": "2025-04-03T14:33:09+00:00" + "time": "2025-06-11T01:04:21+00:00" }, { "name": "phar-io/manifest", @@ -7125,16 +6947,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.18", + "version": "11.5.22", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fc3e887c7f3f9917e1bf61e523413d753db00a17" + "reference": "4cd72faaa8f811e4cc63040cba167757660a5538" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc3e887c7f3f9917e1bf61e523413d753db00a17", - "reference": "fc3e887c7f3f9917e1bf61e523413d753db00a17", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4cd72faaa8f811e4cc63040cba167757660a5538", + "reference": "4cd72faaa8f811e4cc63040cba167757660a5538", "shasum": "" }, "require": { @@ -7144,7 +6966,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.0", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -7157,7 +6979,7 @@ "sebastian/code-unit": "^3.0.3", "sebastian/comparator": "^6.3.1", "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", @@ -7206,7 +7028,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.18" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.22" }, "funding": [ { @@ -7230,7 +7052,7 @@ "type": "tidelift" } ], - "time": "2025-04-22T06:09:49+00:00" + "time": "2025-06-06T02:48:05+00:00" }, { "name": "sebastian/cli-parser", @@ -7609,23 +7431,23 @@ }, { "name": "sebastian/environment", - "version": "7.2.0", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -7661,15 +7483,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2024-07-03T04:54:44+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", @@ -8212,16 +8046,16 @@ }, { "name": "symfony/yaml", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912" + "reference": "cea40a48279d58dc3efee8112634cb90141156c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", - "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", + "url": "https://api.github.com/repos/symfony/yaml/zipball/cea40a48279d58dc3efee8112634cb90141156c2", + "reference": "cea40a48279d58dc3efee8112634cb90141156c2", "shasum": "" }, "require": { @@ -8264,7 +8098,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.5" + "source": "https://github.com/symfony/yaml/tree/v7.3.0" }, "funding": [ { @@ -8280,7 +8114,7 @@ "type": "tidelift" } ], - "time": "2025-03-03T07:12:39+00:00" + "time": "2025-04-04T10:10:33+00:00" }, { "name": "theseer/tokenizer", @@ -8335,12 +8169,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.4" + "php": "^8.4", + "ext-grpc": "*" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/config/cache.php b/config/cache.php index 925f7d2..ced0f02 100644 --- a/config/cache.php +++ b/config/cache.php @@ -71,12 +71,6 @@ ], ], - 'redis' => [ - 'driver' => 'redis', - 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), - 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), - ], - 'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), @@ -86,10 +80,6 @@ 'endpoint' => env('DYNAMODB_ENDPOINT'), ], - 'octane' => [ - 'driver' => 'octane', - ], - ], /* diff --git a/config/database.php b/config/database.php index 8910562..af51ab5 100644 --- a/config/database.php +++ b/config/database.php @@ -141,34 +141,4 @@ | */ - 'redis' => [ - - 'client' => env('REDIS_CLIENT', 'phpredis'), - - 'options' => [ - 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), - 'persistent' => env('REDIS_PERSISTENT', false), - ], - - 'default' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - ], - - 'cache' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_CACHE_DB', '1'), - ], - - ], - ]; diff --git a/config/octane.php b/config/octane.php deleted file mode 100644 index 671915a..0000000 --- a/config/octane.php +++ /dev/null @@ -1,224 +0,0 @@ - env('OCTANE_SERVER', 'roadrunner'), - - /* - |-------------------------------------------------------------------------- - | Force HTTPS - |-------------------------------------------------------------------------- - | - | When this configuration value is set to "true", Octane will inform the - | framework that all absolute links must be generated using the HTTPS - | protocol. Otherwise your links may be generated using plain HTTP. - | - */ - - 'https' => env('OCTANE_HTTPS', true), - - /* - |-------------------------------------------------------------------------- - | Octane Listeners - |-------------------------------------------------------------------------- - | - | All of the event listeners for Octane's events are defined below. These - | listeners are responsible for resetting your application's state for - | the next request. You may even add your own listeners to the list. - | - */ - - 'listeners' => [ - WorkerStarting::class => [ - EnsureUploadedFilesAreValid::class, - EnsureUploadedFilesCanBeMoved::class, - ], - - RequestReceived::class => [ - ...Octane::prepareApplicationForNextOperation(), - ...Octane::prepareApplicationForNextRequest(), - // - ], - - RequestHandled::class => [ - // - ], - - RequestTerminated::class => [ - // FlushUploadedFiles::class, - ], - - TaskReceived::class => [ - ...Octane::prepareApplicationForNextOperation(), - // - ], - - TaskTerminated::class => [ - // - ], - - TickReceived::class => [ - ...Octane::prepareApplicationForNextOperation(), - // - ], - - TickTerminated::class => [ - // - ], - - OperationTerminated::class => [ - FlushOnce::class, - FlushTemporaryContainerInstances::class, - // DisconnectFromDatabases::class, - // CollectGarbage::class, - ], - - WorkerErrorOccurred::class => [ - ReportException::class, - StopWorkerIfNecessary::class, - ], - - WorkerStopping::class => [ - CloseMonologHandlers::class, - ], - ], - - /* - |-------------------------------------------------------------------------- - | Warm / Flush Bindings - |-------------------------------------------------------------------------- - | - | The bindings listed below will either be pre-warmed when a worker boots - | or they will be flushed before every new request. Flushing a binding - | will force the container to resolve that binding again when asked. - | - */ - - 'warm' => [ - ...Octane::defaultServicesToWarm(), - ], - - 'flush' => [ - // - ], - - /* - |-------------------------------------------------------------------------- - | Octane Swoole Tables - |-------------------------------------------------------------------------- - | - | While using Swoole, you may define additional tables as required by the - | application. These tables can be used to store data that needs to be - | quickly accessed by other workers on the particular Swoole server. - | - */ - - 'tables' => [ - 'example:1000' => [ - 'name' => 'string:1000', - 'votes' => 'int', - ], - ], - - /* - |-------------------------------------------------------------------------- - | Octane Swoole Cache Table - |-------------------------------------------------------------------------- - | - | While using Swoole, you may leverage the Octane cache, which is powered - | by a Swoole table. You may set the maximum number of rows as well as - | the number of bytes per row using the configuration options below. - | - */ - - 'cache' => [ - 'rows' => 1000, - 'bytes' => 10000, - ], - - /* - |-------------------------------------------------------------------------- - | File Watching - |-------------------------------------------------------------------------- - | - | The following list of files and directories will be watched when using - | the --watch option offered by Octane. If any of the directories and - | files are changed, Octane will automatically reload your workers. - | - */ - - 'watch' => [ - 'app', - 'bootstrap', - 'config/**/*.php', - 'database/**/*.php', - 'public/**/*.php', - 'resources/**/*.php', - 'routes', - 'composer.lock', - '.env', - ], - - /* - |-------------------------------------------------------------------------- - | Garbage Collection Threshold - |-------------------------------------------------------------------------- - | - | When executing long-lived PHP scripts such as Octane, memory can build - | up before being cleared by PHP. You can force Octane to run garbage - | collection if your application consumes this amount of megabytes. - | - */ - - 'garbage' => 50, - - /* - |-------------------------------------------------------------------------- - | Maximum Execution Time - |-------------------------------------------------------------------------- - | - | The following setting configures the maximum execution time for requests - | being handled by Octane. You may set this value to 0 to indicate that - | there isn't a specific time limit on Octane request execution time. - | - */ - - 'max_execution_time' => 60, - -]; diff --git a/config/queue.php b/config/queue.php index 116bd8d..fc82a42 100644 --- a/config/queue.php +++ b/config/queue.php @@ -63,15 +63,6 @@ 'after_commit' => false, ], - 'redis' => [ - 'driver' => 'redis', - 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), - 'queue' => env('REDIS_QUEUE', 'default'), - 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), - 'block_for' => null, - 'after_commit' => false, - ], - ], /* diff --git a/config/services.php b/config/services.php index 85c8b10..a671c62 100644 --- a/config/services.php +++ b/config/services.php @@ -39,5 +39,13 @@ 'user_url' => env('USER_SERVICE_URL', 'http://user-webserver/'), 'product_url' => env('PRODUCT_SERVICE_URL', 'http://product-webserver/'), 'order_url' => env('ORDER_SERVICE_URL', 'http://order-webserver/'), + 'ai_url' => env('AI_SERVICE_URL', 'http://ai-service/'), + ], + + 'microservices_grpc' => [ + 'UserService' => env('USER_SERVICE_GRPC', 'user:50051'), + 'ProductService' => env('PRODUCT_SERVICE_GRPC', 'product:50051'), + 'OrderService' => env('ORDER_SERVICE_GRPC', 'order:50051'), + 'AIService' => env('AI_SERVICE_GRPC', 'ai-service:50051'), ] ]; diff --git a/docker-compose.yml b/docker-compose.yml index e228f14..e4b07ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: api-gw: build: context: . - image: ommrgazar315/api-gw:2.0 + image: ommrgazar315/api-gw:2.5_grpc container_name: api-gw restart: unless-stopped working_dir: /var/www @@ -31,11 +31,14 @@ services: - DB_DATABASE=api-gw_db - DB_USERNAME=laravel - DB_PASSWORD=api-gw_pass - - BROADCAST_DRIVER=redis - - CACHE_DRIVER=redis - - QUEUE_CONNECTION=redis - - SESSION_DRIVER=redis - - REDIS_HOST=redis + - PRODUCT_SERVICE_URL=http://product-webserver/ + - USER_SERVICE_URL=http://user-webserver/ + - ORDER_SERVICE_URL=http://order-webserver/ + - AI_SERVICE_URL=http://ai-service/ + - USER_SERVICE_GRPC=user:50051 + - PRODUCT_SERVICE_GRPC=product:50051 + - ORDER_SERVICE_GRPC=order:50051 + - AI_SERVICE_GRPC=ai-service:50051 api-gw_db: image: mysql:8.0 diff --git a/docker-composeServer.yml b/docker-composeServer.yml index e13daf2..a32509f 100644 --- a/docker-composeServer.yml +++ b/docker-composeServer.yml @@ -12,11 +12,6 @@ services: - DB_DATABASE=api-gw_db - DB_USERNAME=laravel - DB_PASSWORD=api-gw_pass - - BROADCAST_DRIVER=redis - - CACHE_DRIVER=redis - - QUEUE_CONNECTION=redis - - SESSION_DRIVER=redis - - REDIS_HOST=redis volumes: api-gw-db: diff --git a/php.ini b/php.ini index 6d05b31..179b1b8 100644 --- a/php.ini +++ b/php.ini @@ -1,5 +1,37 @@ +; OPcache settings opcache.enable=1 opcache.enable_cli=1 opcache.memory_consumption=128 +opcache.interned_strings_buffer=8 opcache.max_accelerated_files=10000 -opcache.validate_timestamps=0 +opcache.revalidate_freq=2 +opcache.save_comments=1 +opcache.fast_shutdown=1 + +; PHP settings +memory_limit=256M +max_execution_time=300 +max_input_time=300 +post_max_size=50M +upload_max_filesize=50M + +; gRPC settings +grpc.enable_fork_support=1 +grpc.poll_strategy=epoll1 +grpc.max_receive_message_length=4194304 +grpc.max_send_message_length=4194304 + +; Error reporting +display_errors=Off +log_errors=On +error_log=/var/log/php_errors.log + +; Process control +pcntl.async_signals=On + +; Timezone +date.timezone=UTC + +; Session (if needed) +session.save_handler=files +session.save_path="/tmp" diff --git a/proto/apigw.proto b/proto/apigw.proto new file mode 100644 index 0000000..d8b2606 --- /dev/null +++ b/proto/apigw.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; + +package gateway; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option php_namespace = "App\\Grpc"; +option php_metadata_namespace = "App\\Grpc\\Gateway"; + +// Generic request that can handle any payload +message FlexibleRequest { + // Service identifier (user-service, product-service, etc.) + string service = 1; + + // Method/endpoint identifier + string method = 2; + + // Generic payload - can be any protobuf message + google.protobuf.Any payload = 3; + + // Request metadata + map headers = 4; + + // Optional request ID for tracing + string request_id = 5; +} + +// Generic response that can handle any payload +message FlexibleResponse { + // Success indicator + bool success = 1; + + // HTTP-like status code + int32 status_code = 2; + + // Generic response data - can be any protobuf message or raw data + google.protobuf.Any data = 3; + + // Error information (if any) + ErrorInfo error = 4; + + // Response metadata + map headers = 5; + + // Request ID for tracing + string request_id = 6; + + // Response timestamp + google.protobuf.Timestamp timestamp = 7; +} + +// Alternative: Pure bytes approach (even more flexible) +message BytesRequest { + string service = 1; + string method = 2; + bytes payload = 3; + string content_type = 4; // "application/json", "application/protobuf", etc. + map headers = 5; + string request_id = 6; +} + +message BytesResponse { + bool success = 1; + int32 status_code = 2; + bytes data = 3; + string content_type = 4; + ErrorInfo error = 5; + map headers = 6; + string request_id = 7; + google.protobuf.Timestamp timestamp = 8; +} + +// Error structure +message ErrorInfo { + string message = 1; + string code = 2; + repeated string details = 3; + map metadata = 4; +} + +// Gateway service definition +service GatewayService { + // Handle any request with protobuf Any + rpc HandleRequest(FlexibleRequest) returns (FlexibleResponse); + + // Handle any request with raw bytes (maximum flexibility) + rpc HandleBytesRequest(BytesRequest) returns (BytesResponse); + + // Streaming support for real-time data + rpc HandleStreamRequest(stream FlexibleRequest) returns (stream FlexibleResponse); +} + +// Optional: Health check service +service HealthService { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} diff --git a/proto/user_service.proto b/proto/user_service.proto new file mode 100644 index 0000000..483327b --- /dev/null +++ b/proto/user_service.proto @@ -0,0 +1,159 @@ +// proto/user_service.proto +syntax = "proto3"; + +package user; + +option php_namespace = "App\\Grpc\\User"; +option php_metadata_namespace = "App\\Grpc\\GPBMetadata\\User"; + +service UserService { + rpc GetUser(GetUserRequest) returns (UserResponse); + rpc CreateUser(CreateUserRequest) returns (UserResponse); + rpc UpdateUser(UpdateUserRequest) returns (UserResponse); + rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse); + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); + rpc AuthenticateUser(AuthenticateRequest) returns (AuthenticateResponse); + rpc GetUserProfile(UserProfileRequest) returns (UserProfileResponse); + rpc LogoutUser(LogoutRequest) returns (LogoutResponse); + rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse); + rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse); + rpc ChangePassword(ChangePasswordRequest) returns (ChangePasswordResponse); +} + +// Request Messages +message GetUserRequest { + int64 id = 1; +} + +message CreateUserRequest { + string name = 1; + string email = 2; + string password = 3; + string role = 4; +} + +message UpdateUserRequest { + int64 id = 1; + string name = 2; + string email = 3; + string password = 4; +} + +message DeleteUserRequest { + int64 id = 1; +} + +message ListUsersRequest { + int32 page = 1; + int32 per_page = 2; + string search = 3; +} + +message AuthenticateRequest { + string email = 1; + string password = 2; +} + +message UserProfileRequest { + repeated string fields = 2; +} + +message LogoutRequest { +} + +message ValidateTokenRequest { + string token = 1; +} + +message RefreshTokenRequest { +} + +message ChangePasswordRequest { + string current_password = 1; + string new_password = 2; +} + +// Data Messages +message User { + int64 id = 1; + string name = 2; + string email = 3; + string role = 4; + string photo_path = 5; + string access_token = 6; + string created_at = 7; + string updated_at = 8; +} + +message CartItem { + int64 id = 1; + string type = 2; + int32 quantity = 3; + double price = 4; +} + +// Response Messages +message UserResponse { + User user = 1; + bool success = 2; + string message = 3; +} + +message DeleteUserResponse { + bool success = 1; + string message = 2; +} + +message ListUsersResponse { + repeated User users = 1; + int32 total = 2; + int32 current_page = 3; + int32 last_page = 4; + bool success = 5; + string message = 6; +} + +message AuthenticateResponse { + User user = 1; + string token = 2; + string token_type = 3; + string expires_at = 4; + bool success = 5; + string message = 6; +} + +message UserProfileResponse { + int64 id = 1; + string name = 2; + string email = 3; + string photo_path = 4; + string role = 5; + repeated CartItem cart_items = 6; + bool success = 7; + string message = 8; +} + +message LogoutResponse { + bool success = 1; + string message = 2; +} + +message ValidateTokenResponse { + User user = 1; + bool valid = 2; + bool success = 3; + string message = 4; +} + +message RefreshTokenResponse { + string token = 1; + string token_type = 2; + string expires_at = 3; + bool success = 4; + string message = 5; +} + +message ChangePasswordResponse { + bool success = 1; + string message = 2; +} diff --git a/routes/api.php b/routes/api.php index e5d20b5..39fdf87 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,19 +1,20 @@ prefix('auth') +Route::controller(AuthApiController::class)->prefix('auth') ->group(function () { Route::post('login', 'login'); Route::post('signup', 'signup'); @@ -23,46 +24,51 @@ Route::resource('users', UserApiController::class)->except(['create', 'edit'])->middleware('authenticate'); Route::get('/userProfile', [UserApiController::class, 'userProfile'])->middleware('authenticate'); -Route::apiResource('/user/shipping-addresses', ShippingAddressController::class)->middleware('authenticate'); -Route::get('/user/current-shipping-address', [ShippingAddressController::class, 'getCurrent'])->middleware('authenticate'); +Route::apiResource('/user/shipping-addresses', ShippingAddressApiController::class)->middleware('authenticate'); +Route::get('/user/current-shipping-address', [ShippingAddressApiController::class, 'getCurrent'])->middleware('authenticate'); -Route::controller(PasswordController::class)->middleware('guest')->group(function () { +Route::controller(PasswordApiController::class)->middleware('guest')->group(function () { Route::post('/forgot-password', 'sendResetLink'); Route::post('/reset-password', 'resetPassword'); }); -Route::post('auth/google/callback', [GoogleController::class, 'handleGoogleCallback']); +Route::post('auth/google/callback', [GoogleApiController::class, 'handleGoogleCallback']); Route::group(['middleware' => 'authenticate', 'prefix' => 'carts'], function () { - Route::get('/', [ShoppingCartController::class, 'index']); - Route::post('/', [ShoppingCartController::class, 'add']); - Route::delete('/{item_type}/{item_id}', [ShoppingCartController::class, 'remove']); - Route::delete('/', [ShoppingCartController::class, 'clear']); + Route::get('/', [ShoppingCartApiController::class, 'index']); + Route::post('/', [ShoppingCartApiController::class, 'add']); + Route::delete('/{item_type}/{item_id}', [ShoppingCartApiController::class, 'remove']); + Route::delete('/', [ShoppingCartApiController::class, 'clear']); }); -Route::controller(WishListController::class)->prefix('wishlists')->middleware('authenticate')->group(function () { - Route::get('/', [WishListController::class, 'index']); - Route::post('/{item_type}/{item_id}', [WishListController::class, 'store']); - Route::delete('/{item_type}/{item_id}', [WishListController::class, 'destroy']); +Route::controller(WishListApiController::class)->prefix('wishlists')->middleware('authenticate')->group(function () { + Route::get('/', [WishListApiController::class, 'index']); + Route::post('/{item_type}/{item_id}', [WishListApiController::class, 'store']); + Route::delete('/clear', [WishListApiController::class, 'clear']); + }); //Product Service -Route::resource('products', ProductController::class) +Route::resource('products', ProductApiController::class) ->except(['update', 'create', 'edit']) ->middleware('authenticate') ->where(['product' => '[0-9]+']); -Route::post('products/{product}', [ProductController::class, 'update']) +Route::post('products/{product}', [ProductApiController::class, 'update']) ->middleware('authenticate') ->where('product', '[0-9]+'); // Offer Service -Route::resource('offers', OfferController::class) +Route::resource('offers', OfferApiController::class) ->except(['create', 'edit']) ->middleware('authenticate') ->where(['offer' => '[0-9]+']); Route::apiResource('/orders', OrderApiController::class)->middleware('authenticate'); +Route::get('/purchase-history', [OrderApiController::class, 'purchaseHistory'])->middleware('authenticate'); + +Route::post('products/batch', [ProductApiController::class, 'batch']); +Route::post('/offers/batch', [OfferApiController::class, 'batch']); -Route::post('products/batch', [ProductController::class, 'batch']); -Route::post('/offers/batch', [OfferController::class, 'batch']); +// AI Service +Route::get('/recommendations', [ApiGatewayController::class, 'getRecommendedProducts'])->middleware('authenticate'); diff --git a/routes/grpc.php b/routes/grpc.php new file mode 100644 index 0000000..2eb66b8 --- /dev/null +++ b/routes/grpc.php @@ -0,0 +1,28 @@ +method('Login', 'AuthController', 'login') + ->method('SignUp', 'AuthController', 'signup') + ->method('Logout', 'AuthController', 'logout') + ->method('ValidateToken', 'AuthController', 'validateToken') + // User Service Methods + ->method('GetUser', 'UserController', 'show') + ->method('UpdateUser', 'UserController', 'update') + ->method('DeleteUser', 'UserController', 'destroy') + ->method('ListUsers', 'UserController', 'index'); + +// Product Service Routes +GrpcRoute::service('ProductService') + ->method('listProducts', 'ProductController', 'index') + ->method('getProduct', 'ProductController', 'show'); + +// Order Service Routes +GrpcRoute::service('OrderService') + ->method('GetOrder', 'OrderController', 'show') + ->method('CreateOrder', 'OrderController', 'store') + ->method('UpdateOrderStatus', 'OrderController', 'updateStatus') + ->method('ListOrders', 'OrderController', 'index'); diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..79b1312 --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,39 @@ +[supervisord] +nodaemon=true +user=root + +[program:php-fpm] +command=/usr/local/sbin/php-fpm --nodaemonize +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +redirect_stderr=true + +[unix_http_server] +file=/var/run/supervisor.sock +chmod=0700 +chown=root:root + +[inet_http_server] +port=127.0.0.1:9001 + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[program:grpc-server] +command=php /var/www/artisan grpc:serve --port=50051 --host=0.0.0.0 +directory=/var/www +autostart=true +autorestart=true +user=www-data +numprocs=1 +redirect_stderr=true +stdout_logfile=/var/log/supervisor/grpc-server.log +stderr_logfile=/var/log/supervisor/grpc-server-error.log +stdout_logfile_maxbytes=10MB +stdout_logfile_backups=3 +stopwaitsecs=10 +priority=100