diff --git a/phinx.php b/phinx.php index 0349f87..b83924f 100644 --- a/phinx.php +++ b/phinx.php @@ -1,35 +1,36 @@ -load(); -$settings = require __DIR__ . '/src/settings.php'; +$settings = require __DIR__.'/src/settings.php'; $app = new \Slim\App($settings); // Set up dependencies -require __DIR__ . '/src/dependencies.php'; +require __DIR__.'/src/dependencies.php'; $db = $container['settings']['database']; return [ 'paths' => [ 'migrations' => 'src/Migrations', - 'seeds' => 'src/Seeds', + 'seeds' => 'src/Seeds', ], 'environments' => [ 'default' => [ 'adapter' => $db['driver'], - 'host' => $db['host'], - 'name' => $db['database'], - 'user' => $db['username'], - 'pass' => $db['password'], + 'host' => $db['host'], + 'name' => $db['database'], + 'user' => $db['username'], + 'pass' => $db['password'], ], 'default_migration_table' => 'migrations', ], 'migration_base_class' => "App\Migrations\Migration", - 'templates' => [ + 'templates' => [ 'file' => 'src/Migrations/Migration.template.php.dist', ], ]; - diff --git a/src/Http/Controller/AuthController.php b/src/Http/Controller/AuthController.php index 7ba2195..bbe8378 100644 --- a/src/Http/Controller/AuthController.php +++ b/src/Http/Controller/AuthController.php @@ -1,10 +1,12 @@ -validator->validate( $request, [ - 'email' => ValidatorRule::notEmpty(), + 'email' => ValidatorRule::notEmpty(), 'password' => ValidatorRule::notEmpty(), ] ); - if($validator->isValid()) { + if ($validator->isValid()) { $token = $this->repository->login( $request->getParsedBodyParam('email'), $request->getParsedBodyParam('password') ); - if(!empty($token)) { + if (!empty($token)) { return $response->withStatus(200)->withJson([ 'status' => 'Success', - 'token' => $token, + 'token' => $token, ]); } return $response->withStatus(401)->withJson([ - 'status' => 'Authentication error', + 'status' => 'Authentication error', 'message' => 'User not found', ]); - } return $response->withStatus(400)->withJson( [ 'status' => 'Validation Error', - 'data' => $validator->getErrors() + 'data' => $validator->getErrors(), ] ); } diff --git a/src/Http/Validators/Validator.php b/src/Http/Validators/Validator.php index 2603fdf..4d63fc7 100644 --- a/src/Http/Validators/Validator.php +++ b/src/Http/Validators/Validator.php @@ -1,4 +1,6 @@ -schema = (new Capsule())->schema(); } + /** * Get the schema builder instance. * diff --git a/src/Models/Translation.php b/src/Models/Translation.php index 67be196..93bdc8a 100644 --- a/src/Models/Translation.php +++ b/src/Models/Translation.php @@ -1,4 +1,6 @@ -sourceText = $text; @@ -70,7 +72,7 @@ public function getSourceText(): string * * @return Translation */ - public function setTargetText(string $text): Translation + public function setTargetText(string $text): self { $this->targetText = $text; @@ -94,7 +96,7 @@ public function getTargetText(): string * * @return Translation */ - public function setSourceLanguage(string $language): Translation + public function setSourceLanguage(string $language): self { $this->sourceLanguage = strtolower($language); @@ -118,7 +120,7 @@ public function getSourceLanguage(): string * * @return Translation */ - public function setTargetLanguage(string $language): Translation + public function setTargetLanguage(string $language): self { $this->targetLanguage = strtolower($language); @@ -142,7 +144,7 @@ public function getTargetLanguage(): string * * @return Translation */ - public function setLoadedFromCache(bool $flag): Translation + public function setLoadedFromCache(bool $flag): self { $this->loadedFromCache = $flag; @@ -168,9 +170,9 @@ public function jsonSerialize(): array { return [ - 'source_text' => $this->getSourceText(), + 'source_text' => $this->getSourceText(), 'source_language' => $this->getSourceLanguage(), - 'target_text' => $this->getTargetText(), + 'target_text' => $this->getTargetText(), 'target_language' => $this->getTargetLanguage(), ]; } diff --git a/src/Repository/AuthRepository.php b/src/Repository/AuthRepository.php index d55381a..26387fc 100644 --- a/src/Repository/AuthRepository.php +++ b/src/Repository/AuthRepository.php @@ -1,10 +1,12 @@ -userTable->where('email', '=', $email); - if($users->exists()) { + if ($users->exists()) { $user = $users->get()->first(); - - if($user && password_verify($password, $user->password)) { + + if ($user && password_verify($password, $user->password)) { $token = JWT::encode([ - 'id' => $user->id, + 'id' => $user->id, 'email' => $user->email, ], $this->settings['secret']); } diff --git a/src/Repository/TranslateRepository.php b/src/Repository/TranslateRepository.php index 051ec34..a3037ed 100644 --- a/src/Repository/TranslateRepository.php +++ b/src/Repository/TranslateRepository.php @@ -1,4 +1,6 @@ -setTargetText($targetText); $saved = $this->saveToCache($translation); - if($saved) { + if ($saved) { return $translation; } return false; } - /** * Delete translation. * @@ -95,10 +96,10 @@ public function delete(string $sourceText, string $sourceLang, string $targetLan { return (bool) $this->cache->del([ $sourceLang - . self::TRANSLATION_DIVIDER - . $sourceText - . self::TRANSLATION_DIVIDER - . $targetLang + .self::TRANSLATION_DIVIDER + .$sourceText + .self::TRANSLATION_DIVIDER + .$targetLang, ]); } @@ -113,7 +114,7 @@ public function getAllTranslations(): Collection $translationCollection = (new Collection($allKeys))->map(function ($item) { return $this->getFromCache($item); }); - + return $translationCollection; } @@ -131,12 +132,12 @@ public function translate(string $text, string $targetLang, string $sourceLang = $translation = new Translation(); try { - if($this->existsInCache($text, $sourceLang, $targetLang)) { + if ($this->existsInCache($text, $sourceLang, $targetLang)) { return $this->getFromCache($sourceLang - . self::TRANSLATION_DIVIDER - . $text - . self::TRANSLATION_DIVIDER - . $targetLang); + .self::TRANSLATION_DIVIDER + .$text + .self::TRANSLATION_DIVIDER + .$targetLang); } // Process DeepL response @@ -155,7 +156,6 @@ public function translate(string $text, string $targetLang, string $sourceLang = $this->saveToCache($translation); return $translation; - } catch (GuzzleException $e) { $this->logger->warning($e->getMessage()); } @@ -170,22 +170,23 @@ public function translate(string $text, string $targetLang, string $sourceLang = * @param string $text * @param string $sourceLang * - * @return ResponseInterface * @throws \GuzzleHttp\Exception\GuzzleException + * + * @return ResponseInterface */ protected function deeplTranslate(string $targetLang, string $text, string $sourceLang = ''): ResponseInterface { return $this->httpClient->request( 'GET', - '/' . $this->settings['api_version'] . '/translate', + '/'.$this->settings['api_version'].'/translate', [ 'query' => [ - 'auth_key' => $this->settings['api_token'], + 'auth_key' => $this->settings['api_token'], 'preserve_formatting' => 1, - 'text' => $text, - 'source_lang' => $sourceLang, - 'target_lang' => $targetLang, - ] + 'text' => $text, + 'source_lang' => $sourceLang, + 'target_lang' => $targetLang, + ], ] ); } @@ -203,10 +204,10 @@ private function existsInCache(string $text, string $sourceLanguage, string $tar { return (bool) $this->cache->exists( $sourceLanguage - . self::TRANSLATION_DIVIDER - . $text - . self::TRANSLATION_DIVIDER - . $targetLanguage + .self::TRANSLATION_DIVIDER + .$text + .self::TRANSLATION_DIVIDER + .$targetLanguage ); } @@ -220,10 +221,10 @@ private function existsInCache(string $text, string $sourceLanguage, string $tar private function saveToCache(Translation $translation): bool { $key = $translation->getSourceLanguage() - . self::TRANSLATION_DIVIDER - . $translation->getSourceText() - . self::TRANSLATION_DIVIDER - . $translation->getTargetLanguage(); + .self::TRANSLATION_DIVIDER + .$translation->getSourceText() + .self::TRANSLATION_DIVIDER + .$translation->getTargetLanguage(); /** @var Status $response */ $response = $this->cache->set($key, $translation->getTargetText()); diff --git a/src/dependencies.php b/src/dependencies.php index b8c9e6a..c709fd1 100644 --- a/src/dependencies.php +++ b/src/dependencies.php @@ -1,14 +1,16 @@ -getContainer(); // Database $container['database'] = function (ContainerInterface $c) { - $capsule = new \Illuminate\Database\Capsule\Manager; + $capsule = new \Illuminate\Database\Capsule\Manager(); $capsule->addConnection($c->get('settings')['database']); $capsule->setAsGlobal(); @@ -19,9 +21,9 @@ // Redis cache $container['cache'] = function (ContainerInterface $c) { $config = [ - 'schema' => $c->get('settings')['redis']['schema'], - 'host' => $c->get('settings')['redis']['host'], - 'port' => $c->get('settings')['redis']['port'], + 'schema' => $c->get('settings')['redis']['schema'], + 'host' => $c->get('settings')['redis']['host'], + 'port' => $c->get('settings')['redis']['port'], 'database' => $c->get('settings')['redis']['database'], ]; @@ -34,6 +36,7 @@ $logger = new Monolog\Logger($settings['name']); $logger->pushProcessor(new Monolog\Processor\UidProcessor()); $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level'])); + return $logger; }; @@ -45,8 +48,8 @@ // Controller $container[TranslateController::class] = function (ContainerInterface $c): TranslateController { $logger = $c->get('logger'); - $cache = $c->get("cache"); - $validator = $c->get("validator"); + $cache = $c->get('cache'); + $validator = $c->get('validator'); $guzzle = new \GuzzleHttp\Client( [ @@ -55,22 +58,21 @@ ); $settings = $c->get('settings')['deepl']; - + $repository = new \App\Repository\TranslateRepository($c, $guzzle, $settings, $logger); return new TranslateController($validator, $repository); }; $container[AuthController::class] = function (ContainerInterface $c): AuthController { - $validator = $c->get("validator"); + $validator = $c->get('validator'); $repository = new \App\Repository\AuthRepository($c); return new AuthController($validator, $repository); }; // If not on production, show errors (remove Slim error handlers) -if($container['settings']['displayErrorDetails'] === true) { +if ($container['settings']['displayErrorDetails'] === true) { unset($container['phpErrorHandler']); unset($container['errorHandler']); } - diff --git a/src/middleware.php b/src/middleware.php index a3bc18f..65c2ce8 100644 --- a/src/middleware.php +++ b/src/middleware.php @@ -1,31 +1,35 @@ -add(new Tuupola\Middleware\CorsMiddleware([ - "origin" => ["*"], - "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], - "headers.allow" => ["Authorization", "If-Match", "If-Unmodified-Since", "Content-Type", "Accept"], - "headers.expose" => ["Etag"], - "credentials" => true, - "cache" => 86400, - "error" => function (\Slim\Http\Response $response, array $arguments) { - $data["status"] = "Forbidden"; - $data["message"] = $arguments["message"]; + 'origin' => ['*'], + 'methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + 'headers.allow' => ['Authorization', 'If-Match', 'If-Unmodified-Since', 'Content-Type', 'Accept'], + 'headers.expose' => ['Etag'], + 'credentials' => true, + 'cache' => 86400, + 'error' => function (\Slim\Http\Response $response, array $arguments) { + $data['status'] = 'Forbidden'; + $data['message'] = $arguments['message']; + return $response ->withStatus(403) - ->withHeader("Content-Type", "application/json") + ->withHeader('Content-Type', 'application/json') ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); - } + }, ])); $app->add(new Tuupola\Middleware\JwtAuthentication([ - "path" => "/api", - "secret" => $_ENV['APP_API_SECRET'], - "error" => function (\Slim\Http\Response $response, array $arguments) { - $data["status"] = "Authentication error"; - $data["message"] = $arguments["message"]; + 'path' => '/api', + 'secret' => $_ENV['APP_API_SECRET'], + 'error' => function (\Slim\Http\Response $response, array $arguments) { + $data['status'] = 'Authentication error'; + $data['message'] = $arguments['message']; + return $response ->withStatus(401) - ->withHeader("Content-Type", "application/json") + ->withHeader('Content-Type', 'application/json') ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); - } + }, ])); diff --git a/src/routes.php b/src/routes.php index 0977807..e2f2d97 100644 --- a/src/routes.php +++ b/src/routes.php @@ -1,24 +1,25 @@ -post('/auth/token', \App\Http\Controller\AuthController::class . ':token')->setName('auth.token'); +$app->post('/auth/token', \App\Http\Controller\AuthController::class.':token')->setName('auth.token'); $app->group('/api/v1', function () { // Translate text $this->get( '/translate/text/{text}/target-lang/{target-lang}[/source-lang/{source-lang}]', - \App\Http\Controller\TranslateController::class . ':translate' + \App\Http\Controller\TranslateController::class.':translate' )->setName('translate'); // Get all cached translations - $this->get('/translation', \App\Http\Controller\TranslateController::class . ':translations') + $this->get('/translation', \App\Http\Controller\TranslateController::class.':translations') ->setName('translation.index'); // Update translation - $this->patch('/translation', \App\Http\Controller\TranslateController::class . ':edit') + $this->patch('/translation', \App\Http\Controller\TranslateController::class.':edit') ->setName('translation.edit'); // Delete translation - $this->delete('/translation', \App\Http\Controller\TranslateController::class . ':delete') + $this->delete('/translation', \App\Http\Controller\TranslateController::class.':delete') ->setName('translation.delete'); }); - diff --git a/src/settings.php b/src/settings.php index 82c030a..d4591da 100644 --- a/src/settings.php +++ b/src/settings.php @@ -1,10 +1,12 @@ - [ 'determineRouteBeforeAppMiddleware' => false, - 'displayErrorDetails' => $_ENV['APP_ENV'] === 'production' ? false : true, - 'addContentLengthHeader' => false, // Allow the web server to send the content-length header + 'displayErrorDetails' => $_ENV['APP_ENV'] === 'production' ? false : true, + 'addContentLengthHeader' => false, // Allow the web server to send the content-length header // JWT (Json Web Tokens) 'jwt' => [ @@ -13,11 +15,11 @@ // Database 'database' => [ - 'driver' => $_ENV['DB_DRIVER'] ?? 'mysql', - 'host' => $_ENV['DB_HOST'] ?? 'localhost', - 'database' => $_ENV['DB_NAME'] ?? 'trnsltr', - 'username' => $_ENV['DB_USER'] ?? 'trnsltr', - 'password' => $_ENV['DB_PASSWORD'] ?? '', + 'driver' => $_ENV['DB_DRIVER'] ?? 'mysql', + 'host' => $_ENV['DB_HOST'] ?? 'localhost', + 'database' => $_ENV['DB_NAME'] ?? 'trnsltr', + 'username' => $_ENV['DB_USER'] ?? 'trnsltr', + 'password' => $_ENV['DB_PASSWORD'] ?? '', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => $_ENV['DB_PREFIX'] ?? '', @@ -25,24 +27,24 @@ // Redis cache 'redis' => [ - 'schema' => 'tcp', - 'host' => $_ENV['REDIS_HOST'] ?? '127.0.0.1', - 'port' => $_ENV['REDIS_PORT'] ?? 6379, + 'schema' => 'tcp', + 'host' => $_ENV['REDIS_HOST'] ?? '127.0.0.1', + 'port' => $_ENV['REDIS_PORT'] ?? 6379, 'database' => $_ENV['REDIS_DATABASE'] ?? 0, ], // Monolog settings 'logger' => [ - 'name' => 'slim-app', - 'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log', + 'name' => 'slim-app', + 'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__.'/../logs/app.log', 'level' => \Monolog\Logger::DEBUG, ], // DeepL translator 'deepl' => [ - 'api_host' => $_ENV['DEEPL_API_HOST'] ?? 'https://api.deepl.com', - 'api_token' => $_ENV['DEEPL_API_TOKEN'], + 'api_host' => $_ENV['DEEPL_API_HOST'] ?? 'https://api.deepl.com', + 'api_token' => $_ENV['DEEPL_API_TOKEN'], 'api_version' => 'v2', - ] + ], ], ]; diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 29132f0..b9899a1 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -57,36 +57,36 @@ protected function tearDown() */ protected function createApplication(): void { - $dotenv = Dotenv::create(__DIR__ . '/..'); + $dotenv = Dotenv::create(__DIR__.'/..'); $dotenv->load(); // Use the application settings - $settings = require __DIR__ . '/../src/settings.php'; + $settings = require __DIR__.'/../src/settings.php'; // Instantiate the application $this->app = $app = new App($settings); // Set up dependencies - require __DIR__ . '/../src/dependencies.php'; + require __DIR__.'/../src/dependencies.php'; // Register middleware if ($this->withMiddleware) { - require __DIR__ . '/../src/middleware.php'; + require __DIR__.'/../src/middleware.php'; } // Register routes - require __DIR__ . '/../src/routes.php'; + require __DIR__.'/../src/routes.php'; } /** - * Make a request to the Api + * Make a request to the Api. * * @param $requestMethod * @param $requestUri - * @param null $requestData + * @param null $requestData * @param array $headers + * @param null $token * - * @param null $token - * - * @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response * @throws \Slim\Exception\MethodNotAllowedException * @throws \Slim\Exception\NotFoundException + * + * @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response */ public function request($requestMethod, $requestUri, $requestData = null, $headers = [], $token = null) { @@ -94,17 +94,18 @@ public function request($requestMethod, $requestUri, $requestData = null, $heade } /** - * Process the application given a request method and URI + * Process the application given a request method and URI. * - * @param string $requestMethod the request method (e.g. GET, POST, etc.) - * @param string $requestUri the request URI - * @param array|object|null $requestData the request data - * @param array $headers - * @param null $token + * @param string $requestMethod the request method (e.g. GET, POST, etc.) + * @param string $requestUri the request URI + * @param array|object|null $requestData the request data + * @param array $headers + * @param null $token * - * @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response * @throws \Slim\Exception\MethodNotAllowedException * @throws \Slim\Exception\NotFoundException + * + * @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response */ public function runApp($requestMethod, $requestUri, $requestData = null, $headers = [], $token = null) { @@ -131,15 +132,14 @@ public function runApp($requestMethod, $requestUri, $requestData = null, $header } // Add token, if exists - if($token !== null) { - $request = $request->withHeader('Authorization', 'Bearer ' . $token); + if ($token !== null) { + $request = $request->withHeader('Authorization', 'Bearer '.$token); } // Set up a response object $response = new Response(); - + // Process the application and Return the response return $this->app->process($request, $response); } - } diff --git a/tests/Feature/AuthTest.php b/tests/Feature/AuthTest.php index 14f1f30..3b9ceb1 100644 --- a/tests/Feature/AuthTest.php +++ b/tests/Feature/AuthTest.php @@ -1,9 +1,9 @@ -request('POST', '/auth/token'); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(400, $response->getStatusCode()); $this->assertEquals('Validation Error', $body['status']); @@ -20,10 +20,10 @@ public function test_auth_validation_error() public function test_auth_unauthorized() { $response = $this->request('POST', '/auth/token', [ - 'email' => 'john.doe@example.com', - 'password' => 'test123' + 'email' => 'john.doe@example.com', + 'password' => 'test123', ]); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(401, $response->getStatusCode()); $this->assertEquals('Authentication error', $body['status']); diff --git a/tests/Feature/TranslateTest.php b/tests/Feature/TranslateTest.php index 46c2182..4242b36 100644 --- a/tests/Feature/TranslateTest.php +++ b/tests/Feature/TranslateTest.php @@ -1,4 +1,6 @@ -request('GET', '/api/v1/translate/text/hello/target-lang/de/source-lang/en', null, [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('Success', $body['status']); @@ -28,7 +30,7 @@ public function test_translate_fail_without_auth() public function test_translate_validation_error() { $response = $this->request('GET', '/api/v1/translate/text/hello/target-lang/e', null, [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(400, $response->getStatusCode()); $this->assertEquals('Validation Error', $body['status']); @@ -38,7 +40,7 @@ public function test_translate_validation_error() public function test_all_translations() { $response = $this->request('GET', '/api/v1/translation', null, [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('Success', $body['status']); @@ -59,7 +61,7 @@ public function test_edit_translation_success() 'target_text' => 'kurz', 'target_lang' => 'de', ], [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('Success', $body['status']); @@ -73,7 +75,7 @@ public function test_edit_translation_validation_error() 'target_text' => 'kurz', 'target_lang' => 'de', ], [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(400, $response->getStatusCode()); $this->assertEquals('Validation Error', $body['status']); @@ -101,17 +103,15 @@ public function test_delete_translation_success() $cache->set( $payload['source_lang'] - . TranslateRepository::TRANSLATION_DIVIDER - . $payload['source_text'] - . TranslateRepository::TRANSLATION_DIVIDER - . $payload['target_lang'] - , + .TranslateRepository::TRANSLATION_DIVIDER + .$payload['source_text'] + .TranslateRepository::TRANSLATION_DIVIDER + .$payload['target_lang'], 'Tasche' ); - $response = $this->request('DELETE', '/api/v1/translation', $payload, [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('Success', $body['status']); @@ -124,7 +124,7 @@ public function test_delete_translation_validation_error() 'source_lang' => 'en', 'target_lang' => 'de', ], [], self::TOKEN); - $body = json_decode((string)$response->getBody(), true); + $body = json_decode((string) $response->getBody(), true); $this->assertEquals(400, $response->getStatusCode()); $this->assertEquals('Validation Error', $body['status']); @@ -136,5 +136,4 @@ public function test_delete_translation_fail_without_auth() $this->assertEquals(401, $response->getStatusCode()); } - }