From 3b211ff552af542b3a53d50694b06666a1682d03 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Thu, 4 Apr 2024 13:32:30 +0400 Subject: [PATCH 01/14] create basic api controller --- src/controllers/Api.php | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/controllers/Api.php diff --git a/src/controllers/Api.php b/src/controllers/Api.php new file mode 100644 index 0000000..a66a6bd --- /dev/null +++ b/src/controllers/Api.php @@ -0,0 +1,38 @@ + 0; + } + + public function __construct() + { + header("Content-Type:application/json"); + } + + public function index(): void + { + if (!$this->validateURLFormat()) { + echo "Invalid API URL" . $_GET["url"]; + return; + } + echo json_encode("hello world"); + } +} From 30333d5ffcc4b9f3f2d7a006cf1cf083de7aea51 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:25:05 +0400 Subject: [PATCH 02/14] make api inaccessible to crawlers --- public/robots.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/robots.txt b/public/robots.txt index 14267e9..9cc4092 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,2 +1,3 @@ User-agent: * -Allow: / \ No newline at end of file +Allow: / +Disallow: /api/ \ No newline at end of file From 9cb5497e1714f03079ea84d15b1bbc0a2b8809c7 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:23:08 +0400 Subject: [PATCH 03/14] refactor loadController, handle api route --- src/core/App.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/core/App.php b/src/core/App.php index 3f98c6a..9b11190 100644 --- a/src/core/App.php +++ b/src/core/App.php @@ -5,6 +5,7 @@ namespace Steamy\Core; use Steamy\Controller\_404; +use Steamy\Controller\API; class App { @@ -16,16 +17,20 @@ public function loadController(): void { $URL = Utility::splitURL(); - $controllerClassName = 'Steamy\\Controller\\' . ucfirst($URL[0]); - - - if (class_exists($controllerClassName)) { - $controller = new $controllerClassName(); - } else { - // Fallback to 404 controller - $controller = new _404(); + switch ($URL[0]) { + case 'api': + (new API())->index(); + break; + default: + $controllerClassName = 'Steamy\\Controller\\' . ucfirst($URL[0]); + + if (class_exists($controllerClassName)) { + // call appropriate controller + (new $controllerClassName())->index(); + } else { + // Fallback to 404 controller + (new _404())->index(); + } } - - $controller->index(); // display contents } } \ No newline at end of file From f9b64b5c06125149feef380cfc7664dc65bcf900 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:23:31 +0400 Subject: [PATCH 04/14] create a namespace for api --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index f3814e8..57d81e0 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "Steamy\\": "src/", "Steamy\\Core\\": "src/core/", "Steamy\\Controller\\": "src/controllers/", + "Steamy\\Controller\\API\\": "src/controllers/api/", "Steamy\\Model\\": "src/models/" } }, From 68598ba0cadee97bff506dfc630b3592466d0717 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:11:29 +0400 Subject: [PATCH 05/14] create api router --- src/controllers/API.php | 54 +++++++++++++++++++++++++++++++++++++++++ src/controllers/Api.php | 38 ----------------------------- 2 files changed, 54 insertions(+), 38 deletions(-) create mode 100644 src/controllers/API.php delete mode 100644 src/controllers/Api.php diff --git a/src/controllers/API.php b/src/controllers/API.php new file mode 100644 index 0000000..0ab4e29 --- /dev/null +++ b/src/controllers/API.php @@ -0,0 +1,54 @@ +resource = Utility::splitURL()[2] ?? ""; + } + + /** + * Checks if root relative url starts with /api/v1 + * @return bool + */ + private function validateURLFormat(): bool + { + return preg_match("/^api\/v1/", $_GET["url"]) > 0; + } + + public function index(): void + { + if (!$this->validateURLFormat()) { + echo "Invalid API URL: " . $_GET["url"]; + die(); + } + + // call appropriate controller to handle resource + $controllerClassName = 'Steamy\\Controller\\API\\' . ucfirst($this->resource); + + if (class_exists($controllerClassName)) { + (new $controllerClassName())->index(); + } else { + echo "Invalid API resource: " . $this->resource; + die(); + } + } +} diff --git a/src/controllers/Api.php b/src/controllers/Api.php deleted file mode 100644 index a66a6bd..0000000 --- a/src/controllers/Api.php +++ /dev/null @@ -1,38 +0,0 @@ - 0; - } - - public function __construct() - { - header("Content-Type:application/json"); - } - - public function index(): void - { - if (!$this->validateURLFormat()) { - echo "Invalid API URL" . $_GET["url"]; - return; - } - echo json_encode("hello world"); - } -} From b900483cc881ccb7fdbb56bafbcd6bf5b49938bd Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:12:15 +0400 Subject: [PATCH 06/14] create api controller for products resource --- src/controllers/api/Products.php | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/controllers/api/Products.php diff --git a/src/controllers/api/Products.php b/src/controllers/api/Products.php new file mode 100644 index 0000000..8ed792c --- /dev/null +++ b/src/controllers/api/Products.php @@ -0,0 +1,34 @@ +toArray(); + } + echo json_encode($result); + } + + public function index(): void + { + switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + $this->getProducts(); + break; + default: + echo json_encode("Error"); + } + } +} \ No newline at end of file From 1d76091e367a02e61adb7bf32f18af29ac0caef0 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:12:40 +0400 Subject: [PATCH 07/14] update return type of getAll in phpdoc --- src/models/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/Product.php b/src/models/Product.php index 9d1eaf8..330b4ef 100644 --- a/src/models/Product.php +++ b/src/models/Product.php @@ -99,7 +99,7 @@ public function toArray(): array } /** - * @return array An array of Product objects + * @return Product[] An array of Product objects */ public static function getAll(): array { From 4749e5642e156ca6ba6bc725fb8037d77b411b96 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:13:10 +0400 Subject: [PATCH 08/14] add note on autoloading --- docs/USAGE_GUIDE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/USAGE_GUIDE.md b/docs/USAGE_GUIDE.md index 7acf738..f367846 100644 --- a/docs/USAGE_GUIDE.md +++ b/docs/USAGE_GUIDE.md @@ -36,3 +36,7 @@ To export database with `mysqldump`: ```bash mysqldump -u root -p cafe > cafe.sql ``` + +## Update autoload files + +Whenever changes are made to the autoload settings in `composer.json`, you must run `composer dump-autoload`. From c6a2c5bb91e568657efc305290224452c7344b55 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:24:48 +0400 Subject: [PATCH 09/14] improve phpdoc --- src/core/Utility.php | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/core/Utility.php b/src/core/Utility.php index 3a28fc3..3125cca 100644 --- a/src/core/Utility.php +++ b/src/core/Utility.php @@ -4,15 +4,18 @@ namespace Steamy\Core; +/** + * Utility class containing various helper functions. + */ class Utility { /** - * Display any data in a formatted block. Use this function + * Displays data in a formatted block. Use this function * for debugging. - * @param $stuff + * @param $stuff mixed some data * @return void */ - public static function show($stuff): void + public static function show(mixed $stuff): void { echo "
"; print_r($stuff); @@ -25,7 +28,7 @@ public static function show($stuff): void * This function retrieves the 'url' parameter from the $_GET array or defaults to 'home', * trims leading and trailing slashes, and then explodes the URL into an array of segments. * - * @return array An array containing the URL segments. + * @return string[] An array containing the URL segments where each segment is in lowercase. */ public static function splitURL(): array { @@ -35,20 +38,19 @@ public static function splitURL(): array /** @noinspection PhpNoReturnAttributeCanBeAddedInspection */ /** - * Redirects website to a page. - * @param $path string relative URL of page + * Redirects user to a page and ends execution of script. + * - `redirect('home')` redirects to `ROOT`. + * - `redirect('shop/products/1')` redirects to `ROOT/shop/products/1`. + * + * @param $relative_url string root-relative URL of page. It must not start with /. * @return void */ - public static function redirect(string $path): void + public static function redirect(string $relative_url): void { - header("Location: " . ROOT . "/" . $path); + header("Location: " . ROOT . "/" . $relative_url); die(); } - - - //Function fuzzySearch will be used to search for products on the shop page. - /** * Perform fuzzy search on an array of strings. * @@ -56,7 +58,7 @@ public static function redirect(string $path): void * an array of strings from the input array that closely match the search term. * It uses the Levenshtein distance algorithm to determine the similarity between * the search term and each string in the array. - * + * * @param string $searchTerm The term to search for. * @param array $strings The array of strings to search within. * @param int $threshold The maximum allowed Levenshtein distance. @@ -75,13 +77,13 @@ public static function fuzzySearch(string $searchTerm, array $strings, int $thre } - /** + /** * Calculates the Levenshtein distance between two strings. * * The Levenshtein distance is a metric to measure the difference between two strings. * It is the minimum number of single-character edits (insertions, deletions, or replaces) * required to change one word into the other. - * + * * @param string $str1 The first string. * @param string $str2 The second string. * @return int The Levenshtein distance between the two strings. @@ -99,7 +101,7 @@ public static function levenshteinDistance(string $str1, string $str2): int $dp[$i][$j] = 0; } } - + // Fill the first row and column of the array for ($i = 0; $i <= $m; $i++) { $dp[$i][0] = $i; @@ -123,5 +125,4 @@ public static function levenshteinDistance(string $str1, string $str2): int // Return the final result, which is the distance between the two strings return $dp[$m][$n]; } - } From a0a3d1a3f93a9509ab5591253607b54daa69fd93 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:29:40 +0400 Subject: [PATCH 10/14] move autoload setup to installation guide --- docs/INSTALLATION_GUIDE.md | 3 +++ docs/USAGE_GUIDE.md | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/INSTALLATION_GUIDE.md b/docs/INSTALLATION_GUIDE.md index cb9f60f..fb5ed64 100644 --- a/docs/INSTALLATION_GUIDE.md +++ b/docs/INSTALLATION_GUIDE.md @@ -110,3 +110,6 @@ In the root directory of the project, run: ```bash npm install ``` +## Autoload setup + +Whenever changes are made to the autoload settings in `composer.json`, you must run `composer dump-autoload`. \ No newline at end of file diff --git a/docs/USAGE_GUIDE.md b/docs/USAGE_GUIDE.md index f367846..98dc589 100644 --- a/docs/USAGE_GUIDE.md +++ b/docs/USAGE_GUIDE.md @@ -35,8 +35,4 @@ To export database with `mysqldump`: ```bash mysqldump -u root -p cafe > cafe.sql -``` - -## Update autoload files - -Whenever changes are made to the autoload settings in `composer.json`, you must run `composer dump-autoload`. +``` \ No newline at end of file From 6262b54cce59a422a9847406eb085b9c51c9f08b Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:57:45 +0400 Subject: [PATCH 11/14] replace login endpoint with session endpoint --- docs/API.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/API.md b/docs/API.md index bb9b4ba..d3a08ce 100644 --- a/docs/API.md +++ b/docs/API.md @@ -20,17 +20,16 @@ There are two types of endpoints: 1. **Public endpoints** : They return a public resource that can be accessed **without a token**. 2. **Protected endpoints** : They return a protected resource that can only be accessed **with a valid JWT token**. -### Authentication +### Session -| Endpoint | Description | Protected | -|---------------------|---------------------------------------------|-----------| -| `GET /api/v1/login` | Authenticates user and returns a JWT token. | No | +| Endpoint | Description | Protected | +|-------------------------|--------------------------------------------------|-----------| +| `POST /api/v1/sessions` | Authenticates admin and creates a session token. | No | Note: -- Only administrators can receive a JWT token. +- Only administrators can receive a session token. - Only administrators can access protected endpoints. -- The JWT token expires after 24 hours and a new one must be requested. ### User From 0bf753c2b3de80465f4e8b092e2228928683f90e Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:58:03 +0400 Subject: [PATCH 12/14] add getByEmail --- src/models/Administrator.php | 45 +++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/models/Administrator.php b/src/models/Administrator.php index e68da2f..ddfafe7 100644 --- a/src/models/Administrator.php +++ b/src/models/Administrator.php @@ -77,9 +77,6 @@ public function save(): void $inserted_record = self::first($user_data, 'user'); -// utility::show("inserted record: "); -// Utility::show($inserted_record); - if (!$inserted_record) { return; } @@ -95,6 +92,48 @@ public function save(): void $this->insert($admin_data, $this->table); } + + /** + * Returns the Administrator object corresponding to the given email. + * + * @param string $email The email of the administrator. + * @return ?Administrator The Client object if found, otherwise null. + */ + public static function getByEmail(string $email): ?Administrator + { + $query = <<$email]); + + // Check if the result is empty + if (!$result) { + return null; + } + + // Create a new Administrator object + $administrator = new Administrator( + email: $email, + first_name: $result->first_name, + last_name: $result->last_name, + plain_password: "dummy", + phone_no: $result->phone_no, + job_title: $result->job_title, + is_super_admin: filter_var($result->is_super_admin, FILTER_SANITIZE_NUMBER_INT) + ); + + // Set the user ID and password hash + $administrator->user_id = $result->user_id; + $administrator->password = $result->password; + + return $administrator; + } + public function getJobTitle(): string { return $this->job_title; From b2f15db5c2d5d34381c0b34f76b23e60ea4aab75 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:59:22 +0400 Subject: [PATCH 13/14] create an api controller for sessions endpoint --- src/controllers/api/Sessions.php | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/controllers/api/Sessions.php diff --git a/src/controllers/api/Sessions.php b/src/controllers/api/Sessions.php new file mode 100644 index 0000000..48d559c --- /dev/null +++ b/src/controllers/api/Sessions.php @@ -0,0 +1,54 @@ +verifyPassword($password)) { + http_response_code(401); + die(); + } + + $_SESSION['admin_email'] = $email; + session_regenerate_id(); + } + + public function index(): void + { + switch ($_SERVER['REQUEST_METHOD']) { + case 'POST': + $this->handleLogin(); + break; + default: + http_response_code(400); + die(); + } + } +} \ No newline at end of file From 14288d67bdd18d60f46137a6bb7e21b1ade2c9c1 Mon Sep 17 00:00:00 2001 From: creme332 <65414576+creme332@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:02:08 +0400 Subject: [PATCH 14/14] handle error better, add incomplete functions for all valid routes --- src/controllers/api/Products.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/controllers/api/Products.php b/src/controllers/api/Products.php index 8ed792c..1b1f78d 100644 --- a/src/controllers/api/Products.php +++ b/src/controllers/api/Products.php @@ -21,14 +21,37 @@ private function getProducts(): void echo json_encode($result); } + private function addProduct(): void + { + } + + private function deleteProduct(): void + { + } + + private function updateProduct(): void + { + } + + public function index(): void { switch ($_SERVER['REQUEST_METHOD']) { case 'GET': $this->getProducts(); break; + case 'POST': + $this->addProduct(); + break; + case 'DELETE': + $this->deleteProduct(); + break; + case 'PUT': + $this->updateProduct(); + break; default: - echo json_encode("Error"); + http_response_code(400); + die(); } } } \ No newline at end of file