Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup project for API #110

Merged
merged 15 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"Steamy\\": "src/",
"Steamy\\Core\\": "src/core/",
"Steamy\\Controller\\": "src/controllers/",
"Steamy\\Controller\\API\\": "src/controllers/api/",
"Steamy\\Model\\": "src/models/"
}
},
Expand Down
11 changes: 5 additions & 6 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions docs/INSTALLATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
2 changes: 1 addition & 1 deletion docs/USAGE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ To export database with `mysqldump`:

```bash
mysqldump -u root -p cafe > resources/database/dump/cafe.sql
```
```
3 changes: 2 additions & 1 deletion public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
User-agent: *
Allow: /
Allow: /
Disallow: /api/
54 changes: 54 additions & 0 deletions src/controllers/API.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Steamy\Controller;

use Steamy\Core\Controller;
use Steamy\Core\Utility;

/**
* Router for API. It is called for URLs of the form `/api/v1/...`
*
* E.g., http://localhost/steamy-sips/public/api/products
*/
class API
{
use Controller;

private string $resource;

public function __construct()
{
header("Content-Type:application/json");

$this->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();
}
}
}
57 changes: 57 additions & 0 deletions src/controllers/api/Products.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Steamy\Controller\API;

use Steamy\Model\Product;

/**
* Handles /products route of api
*/
class Products
{
private function getProducts(): void
{
$all_products = Product::getAll();
$result = [];
foreach ($all_products as $product) {
$result[] = $product->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();
}
}
}
54 changes: 54 additions & 0 deletions src/controllers/api/Sessions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Steamy\Controller\API;

use Steamy\Model\Administrator;

/**
* Handles /sessions route of API
*/
class Sessions
{
private function handleLogin(): void
{
$email = trim($_POST['email'] ?? "");
$password = trim($_POST['password'] ?? "");

if (empty($email) || empty($password)) {
http_response_code(400);
die();
}

// fetch administrator account
$admin = Administrator::getByEmail($email);

// validate email
if (!$admin) {
http_response_code(401);
die();
}

// validate password
if (!$admin->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();
}
}
}
25 changes: 15 additions & 10 deletions src/core/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Steamy\Core;

use Steamy\Controller\_404;
use Steamy\Controller\API;

class App
{
Expand All @@ -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
}
}
35 changes: 18 additions & 17 deletions src/core/Utility.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<pre>";
print_r($stuff);
Expand All @@ -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
{
Expand All @@ -35,28 +38,27 @@ 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.
*
* This function takes a search term and an array of strings, and returns
* 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.
Expand All @@ -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.
Expand All @@ -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;
Expand All @@ -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];
}

}
Loading