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/" } }, 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 diff --git a/docs/INSTALLATION_GUIDE.md b/docs/INSTALLATION_GUIDE.md index 4631ff3..2e7ffec 100644 --- a/docs/INSTALLATION_GUIDE.md +++ b/docs/INSTALLATION_GUIDE.md @@ -116,3 +116,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 93b89e6..c778dbf 100644 --- a/docs/USAGE_GUIDE.md +++ b/docs/USAGE_GUIDE.md @@ -35,4 +35,4 @@ To export database with `mysqldump`: ```bash mysqldump -u root -p cafe > resources/database/dump/cafe.sql -``` +``` \ No newline at end of file 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 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/Products.php b/src/controllers/api/Products.php new file mode 100644 index 0000000..1b1f78d --- /dev/null +++ b/src/controllers/api/Products.php @@ -0,0 +1,57 @@ +toArray(); + } + 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: + http_response_code(400); + die(); + } + } +} \ No newline at end of file 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 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 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];
     }
-
 }
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;
diff --git a/src/models/Product.php b/src/models/Product.php
index 11461ae..fb16124 100644
--- a/src/models/Product.php
+++ b/src/models/Product.php
@@ -103,7 +103,7 @@ public function toArray(): array
     }
 
     /**
-     * @return array An array of Product objects
+     * @return Product[] An array of Product objects
      */
     public static function getAll(): array
     {