Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Console/Commands/GrpcServeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function handle(): int
$server->addHttp2Port("{$host}:{$port}");

// Register the User service
$server->handle(app(\App\Grpc\Services\UserServer::class));
$server->handle(app(\App\Grpc\Services\UserGrpcService::class));

$this->info('gRPC User Service started successfully');
$this->info('Listening for requests...');
Expand Down
29 changes: 0 additions & 29 deletions app/Grpc/BaseResponse.php

This file was deleted.

161 changes: 161 additions & 0 deletions app/Grpc/Controllers/AuthController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace App\Grpc\Controllers;

use App\Grpc\ErrorInfo;
use App\Grpc\FlexibleRequest;
use App\Grpc\FlexibleResponse;
use App\Grpc\Middleware\GrpcAuthMiddleware;
use App\Http\Requests\Users\UserCreateRequest;
use App\Http\Requests\Users\UserReadRequest;
use App\Http\Resources\UserResource;
use App\Services\UserService;
use Exception;
use Google\Protobuf\Any;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response;
use const Grpc\STATUS_INVALID_ARGUMENT;
use const Grpc\STATUS_UNAUTHENTICATED;

class AuthController extends BaseGrpcService
{
public function __construct(
protected readonly UserService $userService
)
{
}

/**
* @throws Exception
*/
public function createUser(FlexibleRequest $request, FlexibleResponse $response): FlexibleResponse
{
// Extract data from Any payload
$userData = $this->getRequestData($request);
$rules = new UserCreateRequest()->rules();
$validator = Validator::make($userData, $rules);
if ($validator->fails()) {
return $this->createErrorResponse(
$response,
Response::HTTP_UNPROCESSABLE_ENTITY,
STATUS_INVALID_ARGUMENT,
$validator->errors(),
errorMsg: ['message' => 'Validation errors']
);
}

// Pack response
$responseData = new Any();
try {
$user = $this->userService->create($userData);
$userResource = new UserResource($user);
$responseData->setValue(json_encode([
'data' => $userResource
]));
$response->setSuccess(true);
$response->setStatusCode(201);
$response->setData($responseData);
} catch (Exception $e) {
Log::error('User creation error', [
'error' => $e->getMessage(),
'trace' => $e->getTrace()
]);
throw $e; // Re-throw to be caught by the outer try-catch
}


return $response;
}

public function login(FlexibleRequest $in, FlexibleResponse $response): FlexibleResponse
{
$data = $this->getRequestData($in);
$rules = new UserReadRequest()->rules();
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
Log::error('User login validation error', [
'errors' => $validator->errors()
]);
self::getContext()->setStatus([
'code' => STATUS_INVALID_ARGUMENT,
'details' => $validator->errors()
]);
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
$response->setError(new ErrorInfo([
'message' => $validator->errors()
]));
$response->setSuccess(false);
return $response;
// throw new Exception($validator->errors(), STATUS_INVALID_ARGUMENT);
}

$user = $this->userService->login($data);

if (!$user) {
self::getContext()->setStatus([
'code' => STATUS_UNAUTHENTICATED,
'details' => 'Invalid credentials'
]);
$response->setError(new ErrorInfo([
'message' => 'Invalid credentials'
]));
$response->setStatusCode(Response::HTTP_UNAUTHORIZED);
$response->setSuccess(false);
return $response;
// throw new Exception ('Invalid credentials', STATUS_UNAUTHENTICATED);
}

// Pack user data into Any
$userResource = new UserResource($user);
$anyData = new Any();
$anyData->setValue(json_encode([
'message' => 'Login successful',
'data' => $userResource
]));

$response->setSuccess(true);
$response->setStatusCode(200);
$response->setData($anyData);

return $response;
}


public function validateToken(FlexibleRequest $request, FlexibleResponse $response): FlexibleResponse
{
try {
$user = auth()->user();
$user->withAccessToken(app(GrpcAuthMiddleware::class)->getToken(BaseGrpcService::getContext()->clientMetadata()));

$user = $this->userService->validateToken($user);
if (is_null($user['data'])) {
return $this->createErrorResponse(
$response,
Response::HTTP_UNAUTHORIZED,
STATUS_INVALID_ARGUMENT,
$user['message'],
);
}
return $this->createResponse([
'message' => $user['message'],
'data' => $user['data']
],
$response
);
} catch (Exception $e) {
Log::error('Token validation error', [
'error' => $e->getMessage(),
'trace' => $e->getTrace()
]);

return $this->createErrorResponse(
$response,
Response::HTTP_UNAUTHORIZED,
STATUS_INVALID_ARGUMENT,
'Token validation failed: ' . $e->getMessage()
);
}
}

}
75 changes: 75 additions & 0 deletions app/Grpc/Controllers/BaseGrpcService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace App\Grpc\Controllers;

use App\Grpc\ErrorInfo;
use App\Grpc\FlexibleRequest;
use App\Grpc\FlexibleResponse;
use Google\Protobuf\Any;
use Grpc\ServerContext;

class BaseGrpcService
{
private static ServerContext $context;

public static function getContext(): ServerContext
{
return self::$context;
}

public static function setContext(ServerContext $context): void
{
self::$context = $context;
}

/**
* @param FlexibleRequest $request
* @return mixed
*/
protected function getRequestData(FlexibleRequest $request): mixed
{
$jsonData = $request->getPayload()->getValue();
return json_decode($jsonData, true);
}


/**
* @param array $data
* @param FlexibleResponse $response
*/
protected function createResponse(array $data, FlexibleResponse $response, $code = 200): FlexibleResponse
{
$responseData = new Any();
$responseData->setValue(json_encode($data));
$response->setData($responseData);
$response->setSuccess(true);
$response->setStatusCode($code);
return $response;
}

/**
* Create error response
*/
public function createErrorResponse(FlexibleResponse $response, int $statusCode, $code, string $message, ?array $errorMsg = null): FlexibleResponse
{
BaseGrpcService::getContext()->setStatus([
'code' => $code,
'details' => $message
]);
$error = new ErrorInfo();
$error->setCode($code);
$error->setMessage($message);

if (!is_null($errorMsg)) {
$any = new Any();
$any->setValue(json_encode($errorMsg));
$response->setData($any);
}

$response->setSuccess(false);
$response->setStatusCode($statusCode);
$response->setError($error);

return $response;
}
}
Loading