From 8cce215ce47b8a46573f474fbba39d7651d63b22 Mon Sep 17 00:00:00 2001 From: armiejean Date: Fri, 11 Apr 2025 11:08:13 +0800 Subject: [PATCH] src/utils files conversion --- app/Helpers/Helper.php | 41 +++++++++++ app/Providers/AppServiceProvider.php | 4 ++ app/Services/ApiService.php | 22 ++++++ app/Services/BaseApiService.php | 57 +++++++++++++++ app/Services/CookieService.php | 55 +++++++++++++++ app/Services/EncryptionService.php | 50 +++++++++++++ app/Services/NotificationApiService.php | 21 ++++++ app/Services/StationApiService.php | 56 +++++++++++++++ composer.json | 6 +- composer.lock | 93 ++++++++++++++++++++++++- config/app.php | 3 + 11 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 app/Helpers/Helper.php create mode 100644 app/Services/BaseApiService.php create mode 100644 app/Services/CookieService.php create mode 100644 app/Services/EncryptionService.php create mode 100644 app/Services/NotificationApiService.php create mode 100644 app/Services/StationApiService.php diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php new file mode 100644 index 0000000..1da3fdc --- /dev/null +++ b/app/Helpers/Helper.php @@ -0,0 +1,41 @@ + $value) { + if ($value) { + if (is_array($value)) { + foreach ($value as $item) { + $query .= http_build_query([$key => $item]) . '&'; + } + } else { + $query .= http_build_query([$key => $value]) . '&'; + } + } + } + + return $query ? '?' . rtrim($query, '&') : ''; + } +} + +if (!function_exists('isEmpty')) { + function isEmpty(array $obj): bool + { + return empty(array_filter($obj)); + } +} + +if (!function_exists('apiFormValidation')) { + function apiFormValidation(array $data, callable $setErrors): void + { + $errors = []; + foreach ($data as $key => $value) { + if (is_array($value) && isset($value[0])) { + $errors[$key] = $value[0]; + } + } + $setErrors($errors); + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f89452e..933ecb5 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,6 +12,10 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton(ApiService::class, function () { return new ApiService(); }); + $this->app->singleton(ApiService::class, fn () => new ApiService()); + $this->app->singleton(NotificationApiService::class, fn () => new NotificationApiService()); + $this->app->singleton(StationApiService::class, fn () => new StationApiService()); + $this->app->singleton(CookieService::class, fn () => new CookieService()); } public function boot() diff --git a/app/Services/ApiService.php b/app/Services/ApiService.php index 45641b3..52b54f0 100644 --- a/app/Services/ApiService.php +++ b/app/Services/ApiService.php @@ -70,4 +70,26 @@ class ApiService throw new \Exception('API error: ' . ($response->body() ?: 'Unknown error')); } } + + public function get($url, $params = []) + { + $token = $this->cookieService->getCookie()['token'] ?? null; + $this->defaultHeaders['Authorization'] = 'Bearer ' . $token; + return $this->makeRequest('get', $url, $params); + } + + public function post($url, $params = []) + { + return $this->makeRequest('post', $url, $params); + } + + public function put($url, $params = []) + { + return $this->makeRequest('put', $url, $params); + } + + public function delete($url, $params = []) + { + return $this->makeRequest('delete', $url, ['params' => $params]); + } } \ No newline at end of file diff --git a/app/Services/BaseApiService.php b/app/Services/BaseApiService.php new file mode 100644 index 0000000..79b1e5c --- /dev/null +++ b/app/Services/BaseApiService.php @@ -0,0 +1,57 @@ +baseUrl = $baseUrl; + } + + protected function makeRequest($method, $url, $params = [], $headers = []) + { + try { + $fullHeaders = array_merge($this->defaultHeaders, $headers, [ + 'Authorization' => 'Bearer ' . Session::get('token', ''), + ]); + + $response = Http::withHeaders($fullHeaders)->{$method}($this->baseUrl . $url, $params); + + if ($response->successful()) { + return $response->json(); + } else { + $this->handleError($response); + return null; + } + } catch (\Exception $e) { + Log::error('API Request Error: ' . $e->getMessage()); + Session::flash('error', 'API request failed: ' . $e->getMessage()); + return null; + } + } + + protected function handleError($response) + { + $status = $response->status(); + + if ($status === 401) { + Session::forget('token'); + throw new \Exception('Session expired. Please login again.'); + } elseif ($status === 422) { + throw new \Exception('Validation error: ' . ($response->body() ?: 'Unknown validation error')); + } elseif ($status === 404) { + throw new \Exception('API resource not found.'); + } else { + throw new \Exception('API error: ' . ($response->body() ?: 'Unknown error')); + } + } +} \ No newline at end of file diff --git a/app/Services/CookieService.php b/app/Services/CookieService.php new file mode 100644 index 0000000..7d62683 --- /dev/null +++ b/app/Services/CookieService.php @@ -0,0 +1,55 @@ +addHours(self::TIME_OUT_HOURS); + $encryptedValue = Crypt::encryptString(json_encode($params)); + + Cookie::queue($name, $encryptedValue, $expiration->diffInMinutes(), '/', null, false, true); + + return true; + } catch (\Exception $e) { + Log::error('Cookie Set Error: ' . $e->getMessage()); + return false; + } + } + + public function getCookie(string $name = self::COOKIE_NAME) + { + $cookie = Cookie::get($name); + + if (!$cookie) { + return null; + } + + try { + return json_decode(Crypt::decryptString($cookie), true); + } catch (\Exception $e) { + Log::error('Cookie Get Error: ' . $e->getMessage()); + return null; + } + } + + public function removeCookie(string $name = self::COOKIE_NAME): bool + { + Cookie::queue(Cookie::forget($name)); + return true; + } +} \ No newline at end of file diff --git a/app/Services/EncryptionService.php b/app/Services/EncryptionService.php new file mode 100644 index 0000000..86cf714 --- /dev/null +++ b/app/Services/EncryptionService.php @@ -0,0 +1,50 @@ +password = $this->getPasswordFromCookie(); + } + + private function getPasswordFromCookie() + { + $cookieService = app(CookieService::class); + $cookie = $cookieService->getCookie(process.env.REACT_APP_TOKEN ?? 'token'); + return $cookie['token'] ?? 'default_password'; // Fallback if token not found + } + + public function encrypt(string $text): string + { + $iv = random_bytes(16); // Initialization vector + $encrypted = openssl_encrypt($text, self::ALGORITHM, $this->password, 0, $iv); + return base64_encode($iv . $encrypted); // Combine IV and encrypted data + } + + public function decrypt(string $text): string|bool + { + $data = base64_decode($text); + $iv = substr($data, 0, 16); + $encrypted = substr($data, 16); + + $decrypted = openssl_decrypt($encrypted, self::ALGORITHM, $this->password, 0, $iv); + + if ($decrypted === false) { + return false; + } + + // Check if the result is hexadecimal (simulating your JS check) + if (preg_match('/^[0-9a-fA-F]+$/', $decrypted)) { + return $decrypted; + } + + return false; + } +} \ No newline at end of file diff --git a/app/Services/NotificationApiService.php b/app/Services/NotificationApiService.php new file mode 100644 index 0000000..71c5dcd --- /dev/null +++ b/app/Services/NotificationApiService.php @@ -0,0 +1,21 @@ +makeRequest('get', $url, $params); + } + + public function postNotification($url, $params = []) + { + return $this->makeRequest('post', $url, $params); + } +} \ No newline at end of file diff --git a/app/Services/StationApiService.php b/app/Services/StationApiService.php new file mode 100644 index 0000000..7abdd9e --- /dev/null +++ b/app/Services/StationApiService.php @@ -0,0 +1,56 @@ +defaultHeaders = array_merge($this->defaultHeaders, [ + 'X-Requested-With' => 'station-locator-api', + ]); + } + + public function getBranch($url, $params = []) + { + return $this->makeRequest('get', $url, $params); + } + + public function postBranch($url, $params = []) + { + return $this->makeRequest('post', $url, $params); + } + + public function putBranch($url, $params = []) + { + return $this->makeRequest('put', $url, $params); + } + + public function deleteBranch($url, $params = []) + { + return $this->makeRequest('delete', $url, ['params' => $params]); + } + + public function postFuel($url, $params = []) + { + return $this->makeRequest('post', $url, $params); + } + + public function putFuel($url, $params = []) + { + return $this->makeRequest('put', $url, $params); + } + + public function getFuels($url, $params = []) + { + return $this->makeRequest('get', $url, $params); + } + + public function getCsv($url, $params = []) + { + return Http::withHeaders(array_merge($this->defaultHeaders, [ + 'Authorization' => 'Bearer ' . Session::get('token', ''), + ]))->get($this->baseUrl . $url, $params)->body(); // Return raw text for CSV + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 16901e0..192f45c 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "laravel/framework": "^11.31", "laravel/tinker": "^2.9", "laravel/ui": "^4.6", - "livewire/livewire": "^3.6" + "livewire/livewire": "^3.6", + "paragonie/sodium_compat": "^2.1" }, "require-dev": { "fakerphp/faker": "^1.23", @@ -24,6 +25,9 @@ "phpunit/phpunit": "^11.0.1" }, "autoload": { + "files": [ + "app/Helpers/Helper.php" + ], "psr-4": { "App\\": "app/", "Database\\Factories\\": "database/factories/", diff --git a/composer.lock b/composer.lock index b33b6bf..bfce4c2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "285c8ee44334e3e198f19e9bd78c85e5", + "content-hash": "76604e550276fef9afccacf4143ff3aa", "packages": [ { "name": "brick/math", @@ -2647,6 +2647,97 @@ ], "time": "2024-11-21T10:39:51+00:00" }, + { + "name": "paragonie/sodium_compat", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a673d5f310477027cead2e2f2b6db5d8368157cb", + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb", + "shasum": "" + }, + "require": { + "php": "^8.1", + "php-64bit": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9", + "vimeo/psalm": "^4|^5" + }, + "suggest": { + "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v2.1.0" + }, + "time": "2024-09-04T12:51:01+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", diff --git a/config/app.php b/config/app.php index f467267..65558c0 100644 --- a/config/app.php +++ b/config/app.php @@ -14,6 +14,9 @@ return [ */ 'name' => env('APP_NAME', 'Laravel'), + 'api_base_url' => env('API_BASE_URL', 'https://your-backend-api.com/api'), + 'notif_api_base_url' => env('NOTIF_API_BASE_URL', 'https://your-backend-api.com/notif'), + 'station_api_base_url' => env('STATION_API_BASE_URL', 'https://your-backend-api.com/station'), /* |--------------------------------------------------------------------------