From 39e9876425e1840f95952eefb90d85f8ac90c672 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 14 Jan 2025 13:45:33 +0530 Subject: [PATCH 01/80] build: added slim for api framework --- .env-simple | 5 +- .gitattributes | 3 - .gitignore | 31 +++++++--- .htaccess | 10 ++-- .vscode/extensions.json | 8 +++ README.md | 25 +------- composer.json | 28 +++------ index.php | 47 ++++----------- php-rest-api.sql | 73 ----------------------- src/controllers/collection.controller.php | 20 ------- src/controllers/database.controller.php | 25 -------- src/controllers/file.controller.php | 67 --------------------- src/controllers/method.controller.php | 53 ---------------- src/models/delete.model.php | 29 --------- src/models/get.model.php | 39 ------------ src/models/post.model.php | 30 ---------- src/models/put.model.php | 33 ---------- src/rest.php | 39 ------------ src/views/access_token.view.php | 27 --------- src/views/header.view.php | 5 -- 20 files changed, 55 insertions(+), 542 deletions(-) delete mode 100644 .gitattributes create mode 100644 .vscode/extensions.json delete mode 100644 php-rest-api.sql delete mode 100644 src/controllers/collection.controller.php delete mode 100644 src/controllers/database.controller.php delete mode 100644 src/controllers/file.controller.php delete mode 100644 src/controllers/method.controller.php delete mode 100644 src/models/delete.model.php delete mode 100644 src/models/get.model.php delete mode 100644 src/models/post.model.php delete mode 100644 src/models/put.model.php delete mode 100644 src/rest.php delete mode 100644 src/views/access_token.view.php delete mode 100644 src/views/header.view.php diff --git a/.env-simple b/.env-simple index 38020fa..a9d3847 100644 --- a/.env-simple +++ b/.env-simple @@ -1,4 +1 @@ -# Database Variables -DATABASE_HOST = '' -DATABASE_NAME = '' -ACCESS_TOKEN = '' \ No newline at end of file +NAME = '' \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 254adde..0000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -/* export-ignore -/src -export-ignore -/composer.json -export-ignore \ No newline at end of file diff --git a/.gitignore b/.gitignore index bb88ab6..e196e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,27 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# See http://help.github.com/ignore-files/ for more about ignoring files. -# dependencies -/vendor/ +# Composer +/vendor composer.lock +.env -# misc -.DS_Store -*.pem +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +# .vscode/* +# .vscode/settings.json +# .vscode/tasks.json +# .vscode/launch.json +# .vscode/extensions.json +# .history/* -# local env files -.env \ No newline at end of file +# System files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/.htaccess b/.htaccess index 94433b0..11b28de 100644 --- a/.htaccess +++ b/.htaccess @@ -1,6 +1,4 @@ -RewriteEngine on -RewriteRule ^access_token=([a-z0-9]+)$ index.php?access_token=$1 -RewriteRule ^([a-z]+)/([a-z0-9]+)/access_token=([a-z0-9]+)$ index.php?collection=$1&&id=$2&&access_token=$3 -RewriteRule ^([a-z]+)/([a-z0-9]+)$ index.php?collection=$1&&id=$2 -RewriteRule ^([a-z]+)/access_token=([a-z0-9]+)$ index.php?collection=$1&&access_token=$2 -RewriteRule ^([a-z]+)$ index.php?collection=$1 \ No newline at end of file +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ index.php [QSA,L] \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7e1343b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": [ + "vscode-icons-team.vscode-icons", + "DEVSENSE.phptools-vscode", + "mhutchie.git-graph" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index b2e7a5f..50c13c9 100644 --- a/README.md +++ b/README.md @@ -1,24 +1 @@ -# Php Rest API Application - -### Installation -Run the Composer command to install the latest stable version of RestJS: - -``` -composer required restjs/php-rest-api -``` - -### Used Feature -> `json`, `headers_authorization`, `upload_file`, `access_token` - -## Quick links -1. [**Get API**](http://localhost/php-rest-api/test/access_token=) for `GET`, `POST`, `PUT`, `DELETE` -2. [**Upload File**](http://localhost/php-rest-api/file/access_token=) for `POST`, `DELETE` - -### API Routes -| HTTP Method | Path | Action | Scope | Desciption | -| ----- | ----- | ----- | ---- |------------- | -| GET | / | index | document:list | Get all document -| POST | / | store | document:create | Create an document -| GET | //{_id} | show | document:read | Fetch an document by id -| PUT | //{_id} | update | document:write | Update an document by id -| DELETE | //{_id} | destroy | document:delete | Delete an document by id \ No newline at end of file +# Php Rest API Application \ No newline at end of file diff --git a/composer.json b/composer.json index 143b30d..f0dea82 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "name": "restjs/php-rest-api", "description": "Create Rest API Application", - "type": "package", "keywords": [ "php", "composer", @@ -9,29 +8,16 @@ "file-upload", "web-services", "jwt-authentication", - "my-sql" + "my-sql", + "slim" ], "homepage": "https://github.com/fullstackondemand/php-rest-api", "license": "MIT", "autoload": { "psr-4": { - "RestJS\\PhpRestApi\\": "src/" + "RestJS\\": "src/" } }, - "repositories": [ - { - "type": "package", - "package": { - "name": "RestJS/php-rest-api", - "version": "v2", - "source": { - "url": "https://github.com/fullstackondemand/php-rest-api.git", - "type": "git", - "reference": "master" - } - } - } - ], "authors": [ { "name": "Pramod Singh", @@ -44,8 +30,8 @@ "prefer-stable": true, "require": { "php": "7.1.*|7.2.*|7.3.*|7.4.*|8.0.*|8.1.*", - "mongodb/mongodb": "^1.12", - "alcaeus/mongo-php-adapter": "^1.2", - "vlucas/phpdotenv": "^5.6" + "vlucas/phpdotenv": "^5.6", + "slim/slim": "4.*", + "slim/psr7": "^1.7" } -} \ No newline at end of file +} diff --git a/index.php b/index.php index 684acbd..6c567e1 100644 --- a/index.php +++ b/index.php @@ -1,42 +1,17 @@ load(); -/* Head Allows Section */ -require __DIR__ . '/src/views/header.view.php'; -$headers = getallheaders(); +$app->get('/api/', function (Request $request, Response $response, $args) { + $response->getBody()->write("Hello {$_ENV['NAME']}!"); + return $response; +}); -/* Check Database Connection */ -require __DIR__ . '/src/controllers/database.controller.php'; -$database = (new Database())->connection(); - -/* Check Authentication */ -require __DIR__ . '/src/views/access_token.view.php'; - -/* Check Collection */ -require __DIR__ . '/src/controllers/collection.controller.php'; -$collection = (new Collection())->Collection($database); - -/* Check ID */ -if (!isset($_GET['id'])) { - $id = null; -} else { - $id = $_GET['id']; -} - -/* Check Upload File */ -if (isset($_GET['collection']) && $_GET['collection'] === "file") { - require __DIR__ . '/src/controllers/file.controller.php'; - (new FileController())->FileController($_SERVER['REQUEST_METHOD']); - die(); -} - -/* Method Controller File */ -require __DIR__ . '/src/controllers/method.controller.php'; -(new MethodController())->methodController($_SERVER['REQUEST_METHOD'], $collection, $id, __DIR__); \ No newline at end of file +$app->run(); \ No newline at end of file diff --git a/php-rest-api.sql b/php-rest-api.sql deleted file mode 100644 index 58edba8..0000000 --- a/php-rest-api.sql +++ /dev/null @@ -1,73 +0,0 @@ --- phpMyAdmin SQL Dump --- version 4.9.0.1 --- https://www.phpmyadmin.net/ --- --- Host: 127.0.0.1 --- Generation Time: Jun 27, 2023 at 07:21 PM --- Server version: 10.3.16-MariaDB --- PHP Version: 7.3.6 - -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET AUTOCOMMIT = 0; -START TRANSACTION; -SET time_zone = "+00:00"; - - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; - --- --- Database: `php-rest-api` --- - --- -------------------------------------------------------- - --- --- Table structure for table `test` --- - -CREATE TABLE `test` ( - `id` int(11) NOT NULL, - `name` varchar(100) NOT NULL, - `email` varchar(100) NOT NULL, - `class` varchar(100) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - --- --- Dumping data for table `test` --- - -INSERT INTO `test` (`id`, `name`, `email`, `class`) VALUES -(1, 'Pramod Singh', 'spramodgusian@gmail.com', 'Xth'), -(2, 'Arjub Singh', 'arjun@gmail.com', '4th'), -(4, 'Pramod Singh', 'spramodgusian@gmail.com', 'Xth'), -(6, 'Arjub Singh', 'spramodgusian@gmail.com', 'Xth'), -(7, 'Arjub Singh', 'spramousian@gmail.com', 'Xth'), -(8, 'Arjub Singh', 'spramousian@gmail.com', 'Xth'); - --- --- Indexes for dumped tables --- - --- --- Indexes for table `test` --- -ALTER TABLE `test` - ADD PRIMARY KEY (`id`); - --- --- AUTO_INCREMENT for dumped tables --- - --- --- AUTO_INCREMENT for table `test` --- -ALTER TABLE `test` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; -COMMIT; - -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/src/controllers/collection.controller.php b/src/controllers/collection.controller.php deleted file mode 100644 index 8c190fd..0000000 --- a/src/controllers/collection.controller.php +++ /dev/null @@ -1,20 +0,0 @@ - 'Fail', 'error' => 'Please provide valid collection.')); - die(); - } - else { - $table = $database->{$_GET['collection']}; - return $table; - } - - } -} \ No newline at end of file diff --git a/src/controllers/database.controller.php b/src/controllers/database.controller.php deleted file mode 100644 index c20cf60..0000000 --- a/src/controllers/database.controller.php +++ /dev/null @@ -1,25 +0,0 @@ -selectDatabase('php-rest-api'); - return $database; - } - - // Catch error to connection - catch (Exception $e) { - printf($e->getMessage()); - } - - } -} \ No newline at end of file diff --git a/src/controllers/file.controller.php b/src/controllers/file.controller.php deleted file mode 100644 index 25c675c..0000000 --- a/src/controllers/file.controller.php +++ /dev/null @@ -1,67 +0,0 @@ - 'Fail', 'error' => 'Please upload png, jpg, jpeg and webp file.')); - die(); - } - - /* Check File Size */ - if ($_FILES["upload"]["size"] > 1024 * 1024) { - echo json_encode(array('status' => 'Fail', 'error' => 'Please upload 1MB size file.')); - die(); - } - - /* Upload File Path */ - $url = "uploads/IMG_" . uniqid() . "_" . date("GHisdmY") . "." . $file_extension; - - /* Upload File */ - if (move_uploaded_file($_FILES['upload']['tmp_name'], $url)) { - - /* Upload File Link */ - $url = __FILE__ . '/' . $url; - - echo json_encode(array('status' => 'Success', 'message' => 'File is successful uploaded.', 'file_url' => $url)); - } - break; - - /* DELETE Method */ - case "DELETE": - - /* Recive Delete File URL */ - $data = json_decode(file_get_contents('php://input'), true); - - /* Remove Host Link in URL */ - $url = str_replace(__FILE__ . '/', "", $data['upload']); - - /* Delete File */ - if (unlink($url)) { - echo json_encode(array('status' => 'Success', 'message' => 'File is deleted.')); - } - break; - - default: - echo json_encode(array('status' => 'Fail', 'error' => 'Please use POST and DELETE Method for file.')); - } - - } -} diff --git a/src/controllers/method.controller.php b/src/controllers/method.controller.php deleted file mode 100644 index fe925e0..0000000 --- a/src/controllers/method.controller.php +++ /dev/null @@ -1,53 +0,0 @@ -getDocument($collection, $id); - break; - - /* POST Method */ - case "POST": - - /* Recive POST Data */ - $data = file_get_contents('php://input'); - - /* POST Model File */ - require $dir . '/src/models/post.model.php'; - (new PostModel())->postDocument($collection, $data); - break; - - /* PUT Method */ - case "PUT": - - /* Recive PUT Data */ - $data = file_get_contents('php://input'); - - /* PUT Model File */ - require $dir . '/src/models/put.model.php'; - (new PutModel())->putDocument($collection, $id, $data); - break; - - /* DELETE Method */ - case "DELETE": - - /* DELETE Model File */ - require $dir . '/src/models/delete.model.php'; - (new DeleteModel())->deleteDocument($collection, $id); - break; - } - - } -} \ No newline at end of file diff --git a/src/models/delete.model.php b/src/models/delete.model.php deleted file mode 100644 index 9f89201..0000000 --- a/src/models/delete.model.php +++ /dev/null @@ -1,29 +0,0 @@ - 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - - /* Delete Document */ - $deleteResult = $collection->deleteOne(['_id' => (new MongoId($id))->toBSONType()]); - - if ($deleteResult->getDeletedCount() == 1) { - echo json_encode(array('status' => 'Success', 'message' => 'Document is Deleted.')); - } - else { - echo json_encode(array('status' => 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - - } -} \ No newline at end of file diff --git a/src/models/get.model.php b/src/models/get.model.php deleted file mode 100644 index c571f0e..0000000 --- a/src/models/get.model.php +++ /dev/null @@ -1,39 +0,0 @@ -find()->toArray(); - - /* BSONObjectID convert to String */ - $getResult = array(); - foreach ($resultArr as $result) { - $result['_id'] = $result->_id->__toString(); - array_push($getResult, $result); - } - - } - - /* Get All Document */ - else { - $getResult = $collection->findOne(["_id" => (new MongoId($id))->toBSONType()]); - $getResult['_id'] = $getResult->_id->__toString(); - } - - if (is_null($getResult)) { - echo json_encode(array('status' => 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - else { - echo json_encode(array('status' => 'Success', 'data' => $getResult)); - } - - } -} diff --git a/src/models/post.model.php b/src/models/post.model.php deleted file mode 100644 index 96f9c9e..0000000 --- a/src/models/post.model.php +++ /dev/null @@ -1,30 +0,0 @@ - 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - - /* Insert Document */ - $data = json_decode($data, true); - $postResult = $collection->insertOne($data); - - if ($postResult->getInsertedCount() == 1) { - echo json_encode(array('status' => 'Success', 'message' => 'Document is Inserted.')); - } - else { - echo json_encode(array('status' => 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - - } -} \ No newline at end of file diff --git a/src/models/put.model.php b/src/models/put.model.php deleted file mode 100644 index 841876d..0000000 --- a/src/models/put.model.php +++ /dev/null @@ -1,33 +0,0 @@ - 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - - /* Update Document */ - $data = json_decode($data, true); - $putResult = $collection->updateOne( - ['_id' => (new MongoId($id))->toBSONType()], - ['$set' => $data] - ); - - if ($putResult->getModifiedCount() == 1) { - echo json_encode(array('status' => 'Success', 'message' => 'Document is Updated.')); - } - else { - echo json_encode(array('status' => 'Fail', 'error' => 'Please provide valid input.')); - die(); - } - - } -} \ No newline at end of file diff --git a/src/rest.php b/src/rest.php deleted file mode 100644 index 163c33e..0000000 --- a/src/rest.php +++ /dev/null @@ -1,39 +0,0 @@ -load(); - - /* Include View Function */ - Auth::accessToken(); - $table = Table::checkTable(); - - /* Check ID */ - if (!isset($_GET['id'])) { $id = null; } - else { $id = $_GET['id']; } - - /* Check Upload File */ - if ($table === "file") { - File::fileUpload($dir, $_SERVER['REQUEST_METHOD']); - die(); - } - - /* Method Controller File */ - Method::methodController($_SERVER['REQUEST_METHOD'], $table, $id); - - } -} \ No newline at end of file diff --git a/src/views/access_token.view.php b/src/views/access_token.view.php deleted file mode 100644 index ed8316f..0000000 --- a/src/views/access_token.view.php +++ /dev/null @@ -1,27 +0,0 @@ - 'Fail', 'error' => 'Please provide valid access token')); - die(); - } -} - -/* Check Authorization */ -else if (isset($headers['Authorization'])) { - - if ($headers['Authorization'] != "Bearer " . $_ENV['ACCESS_TOKEN']) { - echo json_encode(array('status' => 'Fail', 'error' => 'Please provide valid access token')); - die(); - } -} - -/* Not Enter Token */ -else { - echo json_encode(array('status' => 'Fail', 'error' => 'Please provide access token')); - die(); -} - -?> \ No newline at end of file diff --git a/src/views/header.view.php b/src/views/header.view.php deleted file mode 100644 index 0546213..0000000 --- a/src/views/header.view.php +++ /dev/null @@ -1,5 +0,0 @@ - Date: Tue, 14 Jan 2025 14:50:59 +0530 Subject: [PATCH 02/80] fix: set base path for deployment --- index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index 6c567e1..e86cae2 100644 --- a/index.php +++ b/index.php @@ -6,10 +6,11 @@ require __DIR__ . '/vendor/autoload.php'; $app = AppFactory::create(); +$app->setBasePath("/api"); $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); -$app->get('/api/', function (Request $request, Response $response, $args) { +$app->get('/', function (Request $request, Response $response, $args) { $response->getBody()->write("Hello {$_ENV['NAME']}!"); return $response; }); From a88bce6f487b40abbb446dfd8ee9e844c2f00084 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 15:37:13 +0530 Subject: [PATCH 03/80] build(deps): added doctrine-orm, php-di-container and symfony-cache --- composer.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index f0dea82..5ae4f6f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,10 @@ "web-services", "jwt-authentication", "my-sql", - "slim" + "slim", + "doctrine-orm", + "symfony-cache", + "php-di-container" ], "homepage": "https://github.com/fullstackondemand/php-rest-api", "license": "MIT", @@ -29,9 +32,11 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": "7.1.*|7.2.*|7.3.*|7.4.*|8.0.*|8.1.*", "vlucas/phpdotenv": "^5.6", "slim/slim": "4.*", - "slim/psr7": "^1.7" + "slim/psr7": "^1.7", + "doctrine/orm": "^2.20", + "php-di/php-di": "^7.0", + "symfony/cache": "^6.0" } } From c4bdfde35d7a5be68f0601f92304ccd075e149a1 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 15:40:41 +0530 Subject: [PATCH 04/80] chore: database and entity setup --- .env-sample | 5 +++++ .env-simple | 1 - index.php | 17 ++++++----------- src/api/category/Category.php | 31 +++++++++++++++++++++++++++++++ src/api/category/controller.php | 17 +++++++++++++++++ src/api/category/model.php | 20 ++++++++++++++++++++ src/database.container.php | 30 ++++++++++++++++++++++++++++++ src/main.php | 13 +++++++++++++ 8 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 .env-sample delete mode 100644 .env-simple create mode 100644 src/api/category/Category.php create mode 100644 src/api/category/controller.php create mode 100644 src/api/category/model.php create mode 100644 src/database.container.php create mode 100644 src/main.php diff --git a/.env-sample b/.env-sample new file mode 100644 index 0000000..71cd559 --- /dev/null +++ b/.env-sample @@ -0,0 +1,5 @@ +# MySQL Connection Variables +SERVER_NAME = +DATABASE_NAME = +USER_NAME = +PASSWORD = \ No newline at end of file diff --git a/.env-simple b/.env-simple deleted file mode 100644 index a9d3847..0000000 --- a/.env-simple +++ /dev/null @@ -1 +0,0 @@ -NAME = '' \ No newline at end of file diff --git a/index.php b/index.php index e86cae2..e3f86f2 100644 --- a/index.php +++ b/index.php @@ -1,18 +1,13 @@ setBasePath("/api"); -$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); +/** Environment Variables */ +$dotenv = \Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); -$app->get('/', function (Request $request, Response $response, $args) { - $response->getBody()->write("Hello {$_ENV['NAME']}!"); - return $response; -}); +/** External Files */ +require 'src/database.container.php'; +require 'src/main.php'; +/** App Execute or Run */ $app->run(); \ No newline at end of file diff --git a/src/api/category/Category.php b/src/api/category/Category.php new file mode 100644 index 0000000..fa8360f --- /dev/null +++ b/src/api/category/Category.php @@ -0,0 +1,31 @@ +getBody()->write(json_encode($this->model->all())); + return $res; + } +} \ No newline at end of file diff --git a/src/api/category/model.php b/src/api/category/model.php new file mode 100644 index 0000000..2807cd1 --- /dev/null +++ b/src/api/category/model.php @@ -0,0 +1,20 @@ +queryBuilder = $entityManager->createQueryBuilder(); + } + + /** Fetch all data */ + public function all(): array { + return $this->queryBuilder->select('q')->from(Table::class, 'q')->getQuery()->getArrayResult(); + } +} \ No newline at end of file diff --git a/src/database.container.php b/src/database.container.php new file mode 100644 index 0000000..82cf18c --- /dev/null +++ b/src/database.container.php @@ -0,0 +1,30 @@ + $_ENV['HOST_NAME'] ?? 'localhost', + 'dbname' => $_ENV['DATABASE_NAME'], + 'user' => $_ENV['USER_NAME'] ?? 'root', + 'password' => $_ENV['PASSWORD'] ?? '', + 'driver' => 'pdo_mysql', +]; + +/** Database Connection */ +$connection = DriverManager::getConnection($config); + +/** Create Container using PHP-DI */ +$container = new Container(); +$container->set(EntityManager::class, fn() => EntityManager::create( + $connection, + ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../src/api/']) +)); + +// Set container to create App with on AppFactory +AppFactory::setContainer($container); \ No newline at end of file diff --git a/src/main.php b/src/main.php new file mode 100644 index 0000000..07418a3 --- /dev/null +++ b/src/main.php @@ -0,0 +1,13 @@ +setBasePath("/api"); // set base path + +/** Router */ +$app->get('/blog', [Controller::class, "findAll"]); \ No newline at end of file From dc382dbb949f1a4692db1cdaf7b7d31be08d1e2d Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 16:31:13 +0530 Subject: [PATCH 05/80] chore: trait for get and set method --- src/api/category/Category.php | 5 +++++ src/trait/GetterAndSetter.php | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/trait/GetterAndSetter.php diff --git a/src/api/category/Category.php b/src/api/category/Category.php index fa8360f..9ac90ea 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -6,10 +6,15 @@ use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; +use RestJS\Trait\GetterAndSetter; #[Entity] #[Table('category')] class Category { + + // Get and Set methods + use GetterAndSetter; + #[Id] #[Column, GeneratedValue] private int $id; diff --git a/src/trait/GetterAndSetter.php b/src/trait/GetterAndSetter.php new file mode 100644 index 0000000..e015b94 --- /dev/null +++ b/src/trait/GetterAndSetter.php @@ -0,0 +1,15 @@ +$name; + } + + public function __set($name, $value) { + $this->$name = $value; + } +} \ No newline at end of file From aba35cdd007123de33b8d8cd8cf202460368a185 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 16:51:36 +0530 Subject: [PATCH 06/80] chore: function and class for response --- composer.json | 3 +++ src/api/category/controller.php | 10 +++++----- src/class/response.php | 18 ++++++++++++++++++ src/function.php | 11 +++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/class/response.php create mode 100644 src/function.php diff --git a/composer.json b/composer.json index 5ae4f6f..cc549fd 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,9 @@ "homepage": "https://github.com/fullstackondemand/php-rest-api", "license": "MIT", "autoload": { + "files": [ + "src/function.php" + ], "psr-4": { "RestJS\\": "src/" } diff --git a/src/api/category/controller.php b/src/api/category/controller.php index 77dd531..59c892e 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -1,17 +1,17 @@ getBody()->write(json_encode($this->model->all())); - return $res; + function findAll($req, $res) { + $result = $this->model->all(); + return response($req, $res, new Response(data: $result)); } } \ No newline at end of file diff --git a/src/class/response.php b/src/class/response.php new file mode 100644 index 0000000..f8898d9 --- /dev/null +++ b/src/class/response.php @@ -0,0 +1,18 @@ +statusCode = $statusCode; + $this->message = $message; + $this->data = $data; + $this->status = $status; + } +} \ No newline at end of file diff --git a/src/function.php b/src/function.php new file mode 100644 index 0000000..6ada2b4 --- /dev/null +++ b/src/function.php @@ -0,0 +1,11 @@ +getBody()->write(json_encode($args)); + return $res; +} \ No newline at end of file From 193252d296ad5842b41aac17fc789f78d96fb57f Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 18:35:32 +0530 Subject: [PATCH 07/80] chore: entity crud operations --- src/api/category/controller.php | 28 ++++++++++++++++++++++++++-- src/api/category/model.php | 27 +++++++++++++++++++++++++++ src/main.php | 8 ++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/api/category/controller.php b/src/api/category/controller.php index 59c892e..af6a90b 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -9,9 +9,33 @@ class Controller { function __construct(private Model $model) { } - /** Fetch All Data */ - function findAll($req, $res) { + /** Fetch all data */ + public function findAll($req, $res) { $result = $this->model->all(); return response($req, $res, new Response(data: $result)); } + + /** Fetch data by id */ + public function findById($req, $res, $args) { + $result = $this->model->find( $args['id']); + return response($req, $res, args: new Response(data: $result)); + } + + /** Delete data by id */ + public function deleteById($req, $res, $args) { + $result = $this->model->delete($args['id']); + return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); + } + + /** Insert data */ + public function create($req, $res, $args) { + $this->model->insert($req->getParsedBody()); + return response($req, $res, new Response(message: "This item has been successfully added.")); + } + + /** Update by id */ + public function updateById($req, $res, $args) { + $result = $this->model->update($req->getParsedBody(), $args["id"]); + return response($req, $res, new Response(message: "This item has been successfully updated.", data: $result)); + } } \ No newline at end of file diff --git a/src/api/category/model.php b/src/api/category/model.php index 2807cd1..e22b5c0 100644 --- a/src/api/category/model.php +++ b/src/api/category/model.php @@ -17,4 +17,31 @@ public function __construct(private EntityManager $entityManager) { public function all(): array { return $this->queryBuilder->select('q')->from(Table::class, 'q')->getQuery()->getArrayResult(); } + + /** Fetch data by id */ + public function find(mixed $value): array { + return $this->queryBuilder->select('q')->from(Table::class, 'q')->where("q.id = :id")->setParameter(":id", $value)->getQuery()->getArrayResult(); + } + + /** Delete data by id */ + public function delete(string $id) { + return $this->queryBuilder->delete(Table::class, 'q')->where("q.id = :id")->setParameter(':id', $id)->getQuery()->execute(); + } + + /** Update data by id */ + public function update(array $args, string $id) { + $result = 0; + foreach ($args as $key => $value) + $result = $this->queryBuilder->update(Table::class, "q")->set("q.$key", ":$key")->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); + return $result; + } + + /** Insert data */ + public function insert(array $args) { + $data = new Table; + foreach ($args as $key => $value) + $data->__set($key, $value); + $this->entityManager->persist($data); + $this->entityManager->flush(); + } } \ No newline at end of file diff --git a/src/main.php b/src/main.php index 07418a3..965265f 100644 --- a/src/main.php +++ b/src/main.php @@ -9,5 +9,9 @@ /** Middlewares */ $app->setBasePath("/api"); // set base path -/** Router */ -$app->get('/blog', [Controller::class, "findAll"]); \ No newline at end of file +/** Routers */ +$app->get('/category/', [Controller::class, "findAll"]); +$app->get('/category/{id:[0-9]+}/', [Controller::class, "findById"]); +$app->put('/category/{id:[0-9]+}/', [Controller::class, "updateById"]); +$app->post('/category/', [Controller::class, "create"]); +$app->delete('/category/{id:[0-9]+}/', [Controller::class, "deleteById"]); \ No newline at end of file From a0b5b172ffb9649fc944c464c18b3c2949205088 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 18:39:42 +0530 Subject: [PATCH 08/80] fix: entity updates --- src/main.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.php b/src/main.php index 965265f..f4a4896 100644 --- a/src/main.php +++ b/src/main.php @@ -8,6 +8,7 @@ /** Middlewares */ $app->setBasePath("/api"); // set base path +$app->addBodyParsingMiddleware(); // It is used to get json and form body data /** Routers */ $app->get('/category/', [Controller::class, "findAll"]); From 29f4e6bf6fd8f91d2a96204465b97508bc1abf80 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 18:47:45 +0530 Subject: [PATCH 09/80] build(deps): added middlewares/trailing-slash --- composer.json | 6 ++++-- src/main.php | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index cc549fd..855787d 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "slim", "doctrine-orm", "symfony-cache", - "php-di-container" + "php-di-container", + "middlewares-trailing-slash" ], "homepage": "https://github.com/fullstackondemand/php-rest-api", "license": "MIT", @@ -40,6 +41,7 @@ "slim/psr7": "^1.7", "doctrine/orm": "^2.20", "php-di/php-di": "^7.0", - "symfony/cache": "^6.0" + "symfony/cache": "^6.0", + "middlewares/trailing-slash": "^2.0" } } diff --git a/src/main.php b/src/main.php index f4a4896..7374139 100644 --- a/src/main.php +++ b/src/main.php @@ -1,14 +1,16 @@ setBasePath("/api"); // set base path -$app->addBodyParsingMiddleware(); // It is used to get json and form body data +$app->setBasePath("/api"); // set base path +$app->addBodyParsingMiddleware(); // It is used to get json and form body data +$app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error /** Routers */ $app->get('/category/', [Controller::class, "findAll"]); From 8b1dc88a52ab13428e8b5e3acafde2d3637d4da3 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 18:53:28 +0530 Subject: [PATCH 10/80] chore: error handler by middleware --- src/main.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.php b/src/main.php index 7374139..bdcdf2c 100644 --- a/src/main.php +++ b/src/main.php @@ -11,6 +11,7 @@ $app->setBasePath("/api"); // set base path $app->addBodyParsingMiddleware(); // It is used to get json and form body data $app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error +$app->addErrorMiddleware(false, false, false); // It is used to get erors /** Routers */ $app->get('/category/', [Controller::class, "findAll"]); From 9f6727d1ce780c961f846fc2662ade70401635b6 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 18:57:22 +0530 Subject: [PATCH 11/80] chore: wrong request handle --- src/api/category/controller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/category/controller.php b/src/api/category/controller.php index af6a90b..c5a054d 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -1,6 +1,7 @@ model->find( $args['id']); + count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); return response($req, $res, args: new Response(data: $result)); } /** Delete data by id */ public function deleteById($req, $res, $args) { $result = $this->model->delete($args['id']); + $result == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); } @@ -36,6 +39,7 @@ public function create($req, $res, $args) { /** Update by id */ public function updateById($req, $res, $args) { $result = $this->model->update($req->getParsedBody(), $args["id"]); + $result == 0 && throw new HttpBadRequestException($req, "Somthing went wrong..."); return response($req, $res, new Response(message: "This item has been successfully updated.", data: $result)); } } \ No newline at end of file From bf032d2b2fdb6ff667587dc6d62eabe3ce5ba39e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 19:15:23 +0530 Subject: [PATCH 12/80] chore: all header allows --- .env-sample | 6 +++++- index.php | 6 ++++++ src/main.php | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.env-sample b/.env-sample index 71cd559..41dc401 100644 --- a/.env-sample +++ b/.env-sample @@ -2,4 +2,8 @@ SERVER_NAME = DATABASE_NAME = USER_NAME = -PASSWORD = \ No newline at end of file +PASSWORD = + +# Broswer Variables +CORS_ORIGIN = * +SHOW_ERROR = 1 or 0 \ No newline at end of file diff --git a/index.php b/index.php index e3f86f2..d3568e9 100644 --- a/index.php +++ b/index.php @@ -5,6 +5,12 @@ $dotenv = \Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); +/** Headers Variables */ +header("Access-Control-Allow-Origin: {$_ENV['CORS_ORIGIN']}"); +header("Content-Type: application/json; charset=UTF-8"); +header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE"); +header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); + /** External Files */ require 'src/database.container.php'; require 'src/main.php'; diff --git a/src/main.php b/src/main.php index bdcdf2c..e6875eb 100644 --- a/src/main.php +++ b/src/main.php @@ -11,7 +11,7 @@ $app->setBasePath("/api"); // set base path $app->addBodyParsingMiddleware(); // It is used to get json and form body data $app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error -$app->addErrorMiddleware(false, false, false); // It is used to get erors +$app->addErrorMiddleware(boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR'])); // It is used to get erors /** Routers */ $app->get('/category/', [Controller::class, "findAll"]); From 415d6f2215a40457cd77fcb53bbae64f793713e9 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 19:28:44 +0530 Subject: [PATCH 13/80] perf: app group route --- src/api/category/router.php | 14 ++++++++++++++ src/main.php | 8 ++------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/api/category/router.php diff --git a/src/api/category/router.php b/src/api/category/router.php new file mode 100644 index 0000000..f6ac690 --- /dev/null +++ b/src/api/category/router.php @@ -0,0 +1,14 @@ +get('/', [Controller::class, "findAll"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "findById"]); + $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); + $router->post('/', [Controller::class, "create"]); + $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); + } +} \ No newline at end of file diff --git a/src/main.php b/src/main.php index e6875eb..c89c629 100644 --- a/src/main.php +++ b/src/main.php @@ -2,7 +2,7 @@ declare(strict_types=1); use Slim\Factory\AppFactory; use Middlewares\TrailingSlash; -use RestJS\Api\Category\Controller; +use RestJS\Api\Category\Router as CategoryRouter; /** Server App initialize */ $app = AppFactory::create(); @@ -14,8 +14,4 @@ $app->addErrorMiddleware(boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR'])); // It is used to get erors /** Routers */ -$app->get('/category/', [Controller::class, "findAll"]); -$app->get('/category/{id:[0-9]+}/', [Controller::class, "findById"]); -$app->put('/category/{id:[0-9]+}/', [Controller::class, "updateById"]); -$app->post('/category/', [Controller::class, "create"]); -$app->delete('/category/{id:[0-9]+}/', [Controller::class, "deleteById"]); \ No newline at end of file +$app->group('/category', CategoryRouter::class); \ No newline at end of file From 091f6c131a3f380267bd09a8cdc14c3abbfb97c2 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 20:05:21 +0530 Subject: [PATCH 14/80] perf: trait for core model and controller functions --- src/api/category/controller.php | 40 +++------------------------- src/api/category/model.php | 43 ++++-------------------------- src/trait/Controller.php | 43 ++++++++++++++++++++++++++++++ src/trait/Model.php | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 src/trait/Controller.php create mode 100644 src/trait/Model.php diff --git a/src/api/category/controller.php b/src/api/category/controller.php index c5a054d..4ef3d36 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -1,45 +1,13 @@ model->all(); - return response($req, $res, new Response(data: $result)); - } - - /** Fetch data by id */ - public function findById($req, $res, $args) { - $result = $this->model->find( $args['id']); - count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); - return response($req, $res, args: new Response(data: $result)); - } - - /** Delete data by id */ - public function deleteById($req, $res, $args) { - $result = $this->model->delete($args['id']); - $result == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); - return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); - } - - /** Insert data */ - public function create($req, $res, $args) { - $this->model->insert($req->getParsedBody()); - return response($req, $res, new Response(message: "This item has been successfully added.")); - } - - /** Update by id */ - public function updateById($req, $res, $args) { - $result = $this->model->update($req->getParsedBody(), $args["id"]); - $result == 0 && throw new HttpBadRequestException($req, "Somthing went wrong..."); - return response($req, $res, new Response(message: "This item has been successfully updated.", data: $result)); - } + /** Use core controller functions */ + use CoreController; } \ No newline at end of file diff --git a/src/api/category/model.php b/src/api/category/model.php index e22b5c0..75c1f57 100644 --- a/src/api/category/model.php +++ b/src/api/category/model.php @@ -1,47 +1,14 @@ queryBuilder = $entityManager->createQueryBuilder(); - } - - /** Fetch all data */ - public function all(): array { - return $this->queryBuilder->select('q')->from(Table::class, 'q')->getQuery()->getArrayResult(); - } - - /** Fetch data by id */ - public function find(mixed $value): array { - return $this->queryBuilder->select('q')->from(Table::class, 'q')->where("q.id = :id")->setParameter(":id", $value)->getQuery()->getArrayResult(); - } - - /** Delete data by id */ - public function delete(string $id) { - return $this->queryBuilder->delete(Table::class, 'q')->where("q.id = :id")->setParameter(':id', $id)->getQuery()->execute(); - } - - /** Update data by id */ - public function update(array $args, string $id) { - $result = 0; - foreach ($args as $key => $value) - $result = $this->queryBuilder->update(Table::class, "q")->set("q.$key", ":$key")->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); - return $result; - } - - /** Insert data */ - public function insert(array $args) { - $data = new Table; - foreach ($args as $key => $value) - $data->__set($key, $value); - $this->entityManager->persist($data); - $this->entityManager->flush(); - } + /** Use core model functions */ + use CoreModel; } \ No newline at end of file diff --git a/src/trait/Controller.php b/src/trait/Controller.php new file mode 100644 index 0000000..ed90077 --- /dev/null +++ b/src/trait/Controller.php @@ -0,0 +1,43 @@ +model->all(); + return response($req, $res, new Response(data: $result)); + } + + /** Fetch data by id */ + public function findById($req, $res, $args) { + $result = $this->model->find($args['id']); + count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); + return response($req, $res, args: new Response(data: $result)); + } + + /** Delete data by id */ + public function deleteById($req, $res, $args) { + $result = $this->model->delete($args['id']); + $result == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); + return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); + } + + /** Insert data */ + public function create($req, $res, $args) { + $this->model->insert($req->getParsedBody()); + return response($req, $res, new Response(message: "This item has been successfully added.")); + } + + /** Update by id */ + public function updateById($req, $res, $args) { + $result = $this->model->update($req->getParsedBody(), $args["id"]); + $result == 0 && throw new HttpBadRequestException($req, "Somthing went wrong..."); + return response($req, $res, new Response(message: "This item has been successfully updated.", data: $result)); + } +} \ No newline at end of file diff --git a/src/trait/Model.php b/src/trait/Model.php new file mode 100644 index 0000000..0846fd5 --- /dev/null +++ b/src/trait/Model.php @@ -0,0 +1,47 @@ +queryBuilder = $entityManager->createQueryBuilder(); + } + + /** Fetch all data */ + public function all(): array { + return $this->queryBuilder->select('q')->from($this->table, 'q')->getQuery()->getArrayResult(); + } + + /** Fetch data by id */ + public function find(mixed $value): array { + return $this->queryBuilder->select('q')->from($this->table, 'q')->where("q.id = :id")->setParameter(":id", $value)->getQuery()->getArrayResult(); + } + + /** Delete data by id */ + public function delete(string $id) { + return $this->queryBuilder->delete($this->table, 'q')->where("q.id = :id")->setParameter(':id', $id)->getQuery()->execute(); + } + + /** Update data by id */ + public function update(array $args, string $id) { + $result = 0; + foreach ($args as $key => $value) + $result = $this->queryBuilder->update($this->table, "q")->set("q.$key", ":$key")->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); + return $result; + } + + /** Insert data */ + public function insert(array $args) { + $data = new $this->table; + foreach ($args as $key => $value) + $data->__set($key, $value); + $this->entityManager->persist($data); + $this->entityManager->flush(); + } +} \ No newline at end of file From 2d2be61f5bdb4bce676574524f573009b4f5a8e7 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 15 Jan 2025 20:21:43 +0530 Subject: [PATCH 15/80] chore: added core model and controller functions --- src/api/category/router.php | 1 + src/trait/Controller.php | 21 +++++++++++++++++---- src/trait/Model.php | 19 ++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/api/category/router.php b/src/api/category/router.php index f6ac690..adf6186 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -7,6 +7,7 @@ class Router { function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); $router->get('/{id:[0-9]+}/', [Controller::class, "findById"]); + $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findBySlug"]); $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); $router->post('/', [Controller::class, "create"]); $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); diff --git a/src/trait/Controller.php b/src/trait/Controller.php index ed90077..477aed5 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -8,21 +8,34 @@ /** Core Controller Functions */ trait Controller { - /** Fetch all data */ - public function findAll($req, $res) { + /** Fetch all data */ + public function findAll($req, $res) { $result = $this->model->all(); return response($req, $res, new Response(data: $result)); } /** Fetch data by id */ public function findById($req, $res, $args) { - $result = $this->model->find($args['id']); + $result = $this->model->where('id', $args['id']); count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); return response($req, $res, args: new Response(data: $result)); } + /** Fetch data By slug */ + public function findBySlug($req, $res, $args) { + $result = $this->model->where('slug', $args['slug']); + count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); + return response($req, $res, args: new Response(data: $result)); + } + + /** Selected content fetch all data */ + public function selectContent($req, $res, $args) { + $result = $this->model->select(['title']); + return response($req, $res, args: new Response(data: $result)); + } + /** Delete data by id */ - public function deleteById($req, $res, $args) { + public function deleteById($req, $res, $args) { $result = $this->model->delete($args['id']); $result == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); diff --git a/src/trait/Model.php b/src/trait/Model.php index 0846fd5..52e9b0e 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -18,13 +18,19 @@ public function all(): array { return $this->queryBuilder->select('q')->from($this->table, 'q')->getQuery()->getArrayResult(); } - /** Fetch data by id */ - public function find(mixed $value): array { - return $this->queryBuilder->select('q')->from($this->table, 'q')->where("q.id = :id")->setParameter(":id", $value)->getQuery()->getArrayResult(); + /** Fetch all data where property has value */ + public function where(string $property, mixed $value): array { + return $this->queryBuilder->select('q')->from($this->table, 'q')->where("q.{$property} = :{$property}")->setParameter(":{$property}", $value)->getQuery()->getArrayResult(); + } + + /** Selected content fetch all data */ + public function select(array $args) { + $args = array_map(fn($item) => $item = "q.{$item}", $args); + return $this->queryBuilder->select($args)->from($this->table, 'q')->getQuery()->getArrayResult(); } /** Delete data by id */ - public function delete(string $id) { + public function delete(string $id) { return $this->queryBuilder->delete($this->table, 'q')->where("q.id = :id")->setParameter(':id', $id)->getQuery()->execute(); } @@ -32,15 +38,14 @@ public function delete(string $id) { public function update(array $args, string $id) { $result = 0; foreach ($args as $key => $value) - $result = $this->queryBuilder->update($this->table, "q")->set("q.$key", ":$key")->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); + $result = $this->queryBuilder->update($this->table, "q")->set("q.$key", ":$key" )->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); return $result; } /** Insert data */ public function insert(array $args) { $data = new $this->table; - foreach ($args as $key => $value) - $data->__set($key, $value); + foreach ($args as $key => $value) $data->__set($key, $value); $this->entityManager->persist($data); $this->entityManager->flush(); } From 84dd2b7e83002f684d87dda18093c0a2c9899931 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 17 Jan 2025 11:02:41 +0530 Subject: [PATCH 16/80] perf: database connection class --- index.php | 4 +++- src/database.container.php | 30 ------------------------------ src/database.php | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 31 deletions(-) delete mode 100644 src/database.container.php create mode 100644 src/database.php diff --git a/index.php b/index.php index d3568e9..3fc35fa 100644 --- a/index.php +++ b/index.php @@ -1,4 +1,6 @@ $_ENV['HOST_NAME'] ?? 'localhost', - 'dbname' => $_ENV['DATABASE_NAME'], - 'user' => $_ENV['USER_NAME'] ?? 'root', - 'password' => $_ENV['PASSWORD'] ?? '', - 'driver' => 'pdo_mysql', -]; - -/** Database Connection */ -$connection = DriverManager::getConnection($config); - -/** Create Container using PHP-DI */ -$container = new Container(); -$container->set(EntityManager::class, fn() => EntityManager::create( - $connection, - ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../src/api/']) -)); - -// Set container to create App with on AppFactory -AppFactory::setContainer($container); \ No newline at end of file diff --git a/src/database.php b/src/database.php new file mode 100644 index 0000000..ab8cccb --- /dev/null +++ b/src/database.php @@ -0,0 +1,35 @@ + $_ENV['HOST_NAME'] ?? 'localhost', + 'dbname' => $_ENV['DATABASE_NAME'], + 'user' => $_ENV['USER_NAME'] ?? 'root', + 'password' => $_ENV['PASSWORD'] ?? '', + 'driver' => 'pdo_mysql', + ]; + + /** Database Connection */ + $connection = DriverManager::getConnection($config); + + /** Create Container using PHP-DI */ + $container = new Container(); + $container->set(EntityManager::class, fn() => EntityManager::create( + $connection, + ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../src/api/']) + )); + + // Set container to create App with on AppFactory + AppFactory::setContainer($container); + } +} \ No newline at end of file From dde08c064a81dcc588ee32567eb5c1072c439951 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 17 Jan 2025 12:07:29 +0530 Subject: [PATCH 17/80] perf: app create class --- index.php | 21 +++++++-------------- src/app.php | 37 +++++++++++++++++++++++++++++++++++++ src/main.php | 17 ----------------- 3 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 src/app.php delete mode 100644 src/main.php diff --git a/index.php b/index.php index 3fc35fa..254ea96 100644 --- a/index.php +++ b/index.php @@ -1,21 +1,14 @@ load(); +/** Create Application */ +$app = App::create(__DIR__); -/** Headers Variables */ -header("Access-Control-Allow-Origin: {$_ENV['CORS_ORIGIN']}"); -header("Content-Type: application/json; charset=UTF-8"); -header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE"); -header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); +/** Routers */ +$app->group('/category', CategoryRouter::class); -/** External Files */ -Database::connection(); -require 'src/main.php'; - -/** App Execute or Run */ +/** Application Execute or Run */ $app->run(); \ No newline at end of file diff --git a/src/app.php b/src/app.php new file mode 100644 index 0000000..8bc7394 --- /dev/null +++ b/src/app.php @@ -0,0 +1,37 @@ +load(); + + /** Headers Variables */ + header("Access-Control-Allow-Origin: {$_ENV['CORS_ORIGIN']}"); + header("Content-Type: application/json; charset=UTF-8"); + header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE"); + header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); + + /** Database Connection Established */ + Database::connection(); + + /** Server Application Initialize */ + $app = AppFactory::create(); + + /** Middlewares */ + $app->setBasePath("/api"); // set base path + $app->addBodyParsingMiddleware(); // It is used to get json and form body data + $app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error + $app->addErrorMiddleware(boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR'])); // It is used to get erors + + return $app; + } +} \ No newline at end of file diff --git a/src/main.php b/src/main.php deleted file mode 100644 index c89c629..0000000 --- a/src/main.php +++ /dev/null @@ -1,17 +0,0 @@ -setBasePath("/api"); // set base path -$app->addBodyParsingMiddleware(); // It is used to get json and form body data -$app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error -$app->addErrorMiddleware(boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR'])); // It is used to get erors - -/** Routers */ -$app->group('/category', CategoryRouter::class); \ No newline at end of file From ca3dd180747bf02e0a15bd373e0507ab434e49bf Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 17 Jan 2025 19:33:54 +0530 Subject: [PATCH 18/80] perf: entity use classes --- src/api/category/Category.php | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/api/category/Category.php b/src/api/category/Category.php index 9ac90ea..6a996b5 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -1,36 +1,35 @@ Date: Sat, 18 Jan 2025 10:10:26 +0530 Subject: [PATCH 19/80] chore: table column name filter --- src/api/category/router.php | 1 + src/trait/Controller.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/category/router.php b/src/api/category/router.php index adf6186..26a5c4b 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -11,5 +11,6 @@ function __invoke(RouteCollectorProxy $router) { $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); $router->post('/', [Controller::class, "create"]); $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); + $router->get('/{filter:[a-z-,_A-Z]+}/', [Controller::class, "selectContent"]); } } \ No newline at end of file diff --git a/src/trait/Controller.php b/src/trait/Controller.php index 477aed5..b72960d 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -30,7 +30,7 @@ public function findBySlug($req, $res, $args) { /** Selected content fetch all data */ public function selectContent($req, $res, $args) { - $result = $this->model->select(['title']); + $result = $this->model->select(explode(",", $args['filter'])); return response($req, $res, args: new Response(data: $result)); } From df3b6b7d6d804edece3afb4643701db5b6a9ae78 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 18 Jan 2025 13:07:42 +0530 Subject: [PATCH 20/80] perf: check null function --- src/function.php | 6 ++++++ src/trait/Controller.php | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/function.php b/src/function.php index 6ada2b4..af556b9 100644 --- a/src/function.php +++ b/src/function.php @@ -1,6 +1,7 @@ getBody()->write(json_encode($args)); return $res; +} + +/** Check Null Function */ +function checkNull($result, $req) { + !boolval($result) && throw new HttpBadRequestException($req, "Something went wrong..."); } \ No newline at end of file diff --git a/src/trait/Controller.php b/src/trait/Controller.php index b72960d..42d1cf2 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -1,9 +1,8 @@ model->where('id', $args['id']); - count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); + checkNull(count($result) , $req); return response($req, $res, args: new Response(data: $result)); } /** Fetch data By slug */ public function findBySlug($req, $res, $args) { $result = $this->model->where('slug', $args['slug']); - count($result) == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); + checkNull(count($result), $req); return response($req, $res, args: new Response(data: $result)); } @@ -37,7 +36,7 @@ public function selectContent($req, $res, $args) { /** Delete data by id */ public function deleteById($req, $res, $args) { $result = $this->model->delete($args['id']); - $result == 0 && throw new HttpBadRequestException($req, "Something went wrong..."); + checkNull($result, $req); return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); } @@ -50,7 +49,7 @@ public function create($req, $res, $args) { /** Update by id */ public function updateById($req, $res, $args) { $result = $this->model->update($req->getParsedBody(), $args["id"]); - $result == 0 && throw new HttpBadRequestException($req, "Somthing went wrong..."); + checkNull($result, $req); return response($req, $res, new Response(message: "This item has been successfully updated.", data: $result)); } } \ No newline at end of file From 6cb9e6dd526caec2bb96257b5a4f3770bd0b7f89 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 18 Jan 2025 13:44:27 +0530 Subject: [PATCH 21/80] chore: one-to-many and many-to-one relationship functions --- src/function.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/function.php b/src/function.php index af556b9..d386bbd 100644 --- a/src/function.php +++ b/src/function.php @@ -14,4 +14,39 @@ function response(Request $req, Response $res, mixed $args) { /** Check Null Function */ function checkNull($result, $req) { !boolval($result) && throw new HttpBadRequestException($req, "Something went wrong..."); +} + +/** Many To One Relationship Function */ +function ManyToOne($many, $one, $property) { + $result = []; + + foreach ($many as $manyItem): + $manyItem[$property] = array_filter($one, fn($item) => $manyItem[$property] == $item['id'])[0]; + array_push($result, $manyItem); + endforeach; + + return $result; +} + +/** One To Many Relationship Function */ +function OneToMany($one, $many, $property) { + $result = []; + + foreach ($one as $oneItem): + + if($oneItem[$property] == null) $oneItem[$property] =[]; + else { + $ids = json_decode($oneItem[$property]); + $oneItem[$property] = []; + + foreach ($ids as $id): + $array = array_filter($many, fn($item) => $id == $item['id']); + array_push($oneItem[$property], ...$array); + endforeach; + } + + array_push($result, $oneItem); + endforeach; + + return $result; } \ No newline at end of file From d11347795be111077485d212aca0668115e1c07e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 09:06:31 +0530 Subject: [PATCH 22/80] refactor: find-by-id and find-by-slug functions --- src/function.php | 6 +++--- src/trait/Controller.php | 19 ++++++------------- src/trait/Model.php | 11 ----------- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/function.php b/src/function.php index d386bbd..2f4b920 100644 --- a/src/function.php +++ b/src/function.php @@ -21,8 +21,8 @@ function ManyToOne($many, $one, $property) { $result = []; foreach ($many as $manyItem): - $manyItem[$property] = array_filter($one, fn($item) => $manyItem[$property] == $item['id'])[0]; - array_push($result, $manyItem); + $manyItem[$property] = array_filter($one, fn($item) => $manyItem[$property] == $item['id']); + array_push($result, ...$manyItem); endforeach; return $result; @@ -34,7 +34,7 @@ function OneToMany($one, $many, $property) { foreach ($one as $oneItem): - if($oneItem[$property] == null) $oneItem[$property] =[]; + if($oneItem[$property] == null) $oneItem[$property] = []; else { $ids = json_decode($oneItem[$property]); $oneItem[$property] = []; diff --git a/src/trait/Controller.php b/src/trait/Controller.php index 42d1cf2..28d6185 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -9,28 +9,21 @@ trait Controller { /** Fetch all data */ public function findAll($req, $res) { - $result = $this->model->all(); - return response($req, $res, new Response(data: $result)); + return response($req, $res, new Response(data: $this->model->all())); } /** Fetch data by id */ public function findById($req, $res, $args) { - $result = $this->model->where('id', $args['id']); - checkNull(count($result) , $req); - return response($req, $res, args: new Response(data: $result)); + $result = array_filter($this->model->all(), fn($item) => $item['id'] == $args['id']); + checkNull(count(...$result) , $req); + return response($req, $res, args: new Response(data: [...$result])); } /** Fetch data By slug */ public function findBySlug($req, $res, $args) { - $result = $this->model->where('slug', $args['slug']); + $result = array_filter($this->model->all(), fn($item) => $item['slug'] == $args['slug']); checkNull(count($result), $req); - return response($req, $res, args: new Response(data: $result)); - } - - /** Selected content fetch all data */ - public function selectContent($req, $res, $args) { - $result = $this->model->select(explode(",", $args['filter'])); - return response($req, $res, args: new Response(data: $result)); + return response($req, $res, args: new Response(data: [...$result])); } /** Delete data by id */ diff --git a/src/trait/Model.php b/src/trait/Model.php index 52e9b0e..e6ee11b 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -18,17 +18,6 @@ public function all(): array { return $this->queryBuilder->select('q')->from($this->table, 'q')->getQuery()->getArrayResult(); } - /** Fetch all data where property has value */ - public function where(string $property, mixed $value): array { - return $this->queryBuilder->select('q')->from($this->table, 'q')->where("q.{$property} = :{$property}")->setParameter(":{$property}", $value)->getQuery()->getArrayResult(); - } - - /** Selected content fetch all data */ - public function select(array $args) { - $args = array_map(fn($item) => $item = "q.{$item}", $args); - return $this->queryBuilder->select($args)->from($this->table, 'q')->getQuery()->getArrayResult(); - } - /** Delete data by id */ public function delete(string $id) { return $this->queryBuilder->delete($this->table, 'q')->where("q.id = :id")->setParameter(':id', $id)->getQuery()->execute(); From db3ac5118f697cb40627b3df0ac227bd7eb39792 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 09:16:48 +0530 Subject: [PATCH 23/80] perf: all data fetch by table controller --- src/api/category/controller.php | 4 +++- src/trait/Controller.php | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/api/category/controller.php b/src/api/category/controller.php index 4ef3d36..e2de2ca 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -6,7 +6,9 @@ class Controller { - function __construct(private Model $model) {} + function __construct(private Model $model) { + $this->result = $this->model->all(); + } /** Use core controller functions */ use CoreController; diff --git a/src/trait/Controller.php b/src/trait/Controller.php index 28d6185..1512dc8 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -7,21 +7,24 @@ /** Core Controller Functions */ trait Controller { + /** Variables Declaration */ + private $result; + /** Fetch all data */ public function findAll($req, $res) { - return response($req, $res, new Response(data: $this->model->all())); + return response($req, $res, new Response(data: $this->result)); } /** Fetch data by id */ public function findById($req, $res, $args) { - $result = array_filter($this->model->all(), fn($item) => $item['id'] == $args['id']); - checkNull(count(...$result) , $req); + $result = array_filter($this->result, fn($item) => $item['id'] == $args['id']); + checkNull($result, $req); return response($req, $res, args: new Response(data: [...$result])); } /** Fetch data By slug */ public function findBySlug($req, $res, $args) { - $result = array_filter($this->model->all(), fn($item) => $item['slug'] == $args['slug']); + $result = array_filter($this->result, fn($item) => $item['slug'] == $args['slug']); checkNull(count($result), $req); return response($req, $res, args: new Response(data: [...$result])); } From 95ce54781ed6d5d06fb46e0243c9718f7afb402a Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 10:22:53 +0530 Subject: [PATCH 24/80] perf: get query filter table column --- src/api/category/router.php | 1 - src/trait/Controller.php | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/api/category/router.php b/src/api/category/router.php index 26a5c4b..adf6186 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -11,6 +11,5 @@ function __invoke(RouteCollectorProxy $router) { $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); $router->post('/', [Controller::class, "create"]); $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); - $router->get('/{filter:[a-z-,_A-Z]+}/', [Controller::class, "selectContent"]); } } \ No newline at end of file diff --git a/src/trait/Controller.php b/src/trait/Controller.php index 1512dc8..feb73d3 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -7,12 +7,26 @@ /** Core Controller Functions */ trait Controller { - /** Variables Declaration */ + /** Variables Declaration */ private $result; /** Fetch all data */ public function findAll($req, $res) { - return response($req, $res, new Response(data: $this->result)); + + /** Variables Declaration */ + $result = $this->result; + $filter = $req->getQueryParams()['filter'] ?? null; + + /** Selected content fetch all data */ + if ($filter): + $result = []; + $filter = explode(",", $filter); + + foreach ($this->result as $item) + array_push($result, array_intersect_key($item, array_flip($filter))); + endif; + + return response($req, $res, new Response(data: $result)); } /** Fetch data by id */ From fc32eb969569e3972fb2dcef715490e15f216644 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 10:28:49 +0530 Subject: [PATCH 25/80] chore: rename all data fetch model functions --- src/api/category/controller.php | 2 +- src/trait/Model.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/category/controller.php b/src/api/category/controller.php index e2de2ca..87c6cbd 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -7,7 +7,7 @@ class Controller { function __construct(private Model $model) { - $this->result = $this->model->all(); + $this->result = $this->model->fetch(); } /** Use core controller functions */ diff --git a/src/trait/Model.php b/src/trait/Model.php index e6ee11b..f80ee9d 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -14,7 +14,7 @@ public function __construct(private EntityManager $entityManager) { } /** Fetch all data */ - public function all(): array { + public function fetch(): array { return $this->queryBuilder->select('q')->from($this->table, 'q')->getQuery()->getArrayResult(); } From 681d51f3ce251a879beb82144ca6433c28020988 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 11:23:00 +0530 Subject: [PATCH 26/80] perf: find by column function --- src/api/category/router.php | 4 ++-- src/trait/Controller.php | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/api/category/router.php b/src/api/category/router.php index adf6186..aa70a05 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -6,8 +6,8 @@ class Router { function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); - $router->get('/{id:[0-9]+}/', [Controller::class, "findById"]); - $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findBySlug"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); + $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); $router->post('/', [Controller::class, "create"]); $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); diff --git a/src/trait/Controller.php b/src/trait/Controller.php index feb73d3..5c7eefd 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -17,7 +17,7 @@ public function findAll($req, $res) { $result = $this->result; $filter = $req->getQueryParams()['filter'] ?? null; - /** Selected content fetch all data */ + /** Selected column fetch all data */ if ($filter): $result = []; $filter = explode(",", $filter); @@ -29,20 +29,14 @@ public function findAll($req, $res) { return response($req, $res, new Response(data: $result)); } - /** Fetch data by id */ - public function findById($req, $res, $args) { - $result = array_filter($this->result, fn($item) => $item['id'] == $args['id']); + /** Fetch data by Column */ + public function findByColumn($req, $res, $args) { + foreach ($args as $key => $value) + $result = array_filter($this->result, fn($item) => $item[$key] == $args[$key]); checkNull($result, $req); return response($req, $res, args: new Response(data: [...$result])); } - /** Fetch data By slug */ - public function findBySlug($req, $res, $args) { - $result = array_filter($this->result, fn($item) => $item['slug'] == $args['slug']); - checkNull(count($result), $req); - return response($req, $res, args: new Response(data: [...$result])); - } - /** Delete data by id */ public function deleteById($req, $res, $args) { $result = $this->model->delete($args['id']); From d9ac01ebdf7ce74072d1f27970ab03a8fe7f23f9 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 12:13:21 +0530 Subject: [PATCH 27/80] chore: one way filter relationship function --- src/function.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/function.php b/src/function.php index 2f4b920..a31f599 100644 --- a/src/function.php +++ b/src/function.php @@ -48,5 +48,20 @@ function OneToMany($one, $many, $property) { array_push($result, $oneItem); endforeach; + return $result; +} + +/** One Way Filter Relationship Function */ +function OneWayFilter($current, $filter, $addProperty, $findProperty) { + $result = []; + + foreach ($current as $currentItem): + $currentItem[$addProperty] = []; + + $array = array_filter($filter, fn($item) => $currentItem['id'] == $item[$findProperty]); + array_push($currentItem[$addProperty], ...$array); + array_push($result, $currentItem); + endforeach; + return $result; } \ No newline at end of file From d2e0dd93dfbcf581b3d5fecd7871ee04205000a1 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 12:41:30 +0530 Subject: [PATCH 28/80] fix: many to one property array --- src/function.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/function.php b/src/function.php index a31f599..b0d96c8 100644 --- a/src/function.php +++ b/src/function.php @@ -21,8 +21,9 @@ function ManyToOne($many, $one, $property) { $result = []; foreach ($many as $manyItem): - $manyItem[$property] = array_filter($one, fn($item) => $manyItem[$property] == $item['id']); - array_push($result, ...$manyItem); + $filter = array_filter($one, fn($item) => $manyItem[$property] == $item['id']); + $manyItem[$property] = [...$filter][0]; + array_push($result, $manyItem); endforeach; return $result; From 8412864d97f609d541df78bd2bf8a4018dac701e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 15:18:47 +0530 Subject: [PATCH 29/80] refactor: all relationship functions --- composer.json | 3 ++- src/function.php | 51 ----------------------------------------- src/relationship.php | 54 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 52 deletions(-) create mode 100644 src/relationship.php diff --git a/composer.json b/composer.json index 855787d..7d907b6 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "license": "MIT", "autoload": { "files": [ - "src/function.php" + "src/function.php", + "src/relationship.php" ], "psr-4": { "RestJS\\": "src/" diff --git a/src/function.php b/src/function.php index b0d96c8..af556b9 100644 --- a/src/function.php +++ b/src/function.php @@ -14,55 +14,4 @@ function response(Request $req, Response $res, mixed $args) { /** Check Null Function */ function checkNull($result, $req) { !boolval($result) && throw new HttpBadRequestException($req, "Something went wrong..."); -} - -/** Many To One Relationship Function */ -function ManyToOne($many, $one, $property) { - $result = []; - - foreach ($many as $manyItem): - $filter = array_filter($one, fn($item) => $manyItem[$property] == $item['id']); - $manyItem[$property] = [...$filter][0]; - array_push($result, $manyItem); - endforeach; - - return $result; -} - -/** One To Many Relationship Function */ -function OneToMany($one, $many, $property) { - $result = []; - - foreach ($one as $oneItem): - - if($oneItem[$property] == null) $oneItem[$property] = []; - else { - $ids = json_decode($oneItem[$property]); - $oneItem[$property] = []; - - foreach ($ids as $id): - $array = array_filter($many, fn($item) => $id == $item['id']); - array_push($oneItem[$property], ...$array); - endforeach; - } - - array_push($result, $oneItem); - endforeach; - - return $result; -} - -/** One Way Filter Relationship Function */ -function OneWayFilter($current, $filter, $addProperty, $findProperty) { - $result = []; - - foreach ($current as $currentItem): - $currentItem[$addProperty] = []; - - $array = array_filter($filter, fn($item) => $currentItem['id'] == $item[$findProperty]); - array_push($currentItem[$addProperty], ...$array); - array_push($result, $currentItem); - endforeach; - - return $result; } \ No newline at end of file diff --git a/src/relationship.php b/src/relationship.php new file mode 100644 index 0000000..aee67ef --- /dev/null +++ b/src/relationship.php @@ -0,0 +1,54 @@ + $manyItem[$property] == $item['id']); + $manyItem[$property] = [...$filter][0]; + array_push($result, $manyItem); + endforeach; + + return $result; +} + +/** One To Many Relationship Function */ +function oneToMany($one, $many, $property) { + $result = []; + + foreach ($one as $oneItem): + + if ($oneItem[$property] == null) $oneItem[$property] = []; + else { + $ids = json_decode($oneItem[$property]); + $oneItem[$property] = []; + + foreach ($ids as $id): + $array = array_filter($many, fn($item) => $id == $item['id']); + array_push($oneItem[$property], ...$array); + endforeach; + } + + array_push($result, $oneItem); + endforeach; + + return $result; +} + +/** One Way Filter Relationship Function */ +function oneWayFilter($current, $filter, $addProperty, $findProperty) { + $result = []; + + foreach ($current as $currentItem): + $currentItem[$addProperty] = []; + + $array = array_filter($filter, fn($item) => $currentItem['id'] == $item[$findProperty]); + array_push($currentItem[$addProperty], ...$array); + array_push($result, $currentItem); + endforeach; + + return $result; +} \ No newline at end of file From ccb105579b55982aef7a4c96a559b003af8a1125 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 18:45:59 +0530 Subject: [PATCH 30/80] chore: author entity setup --- index.php | 2 ++ src/api/author/Author.php | 32 ++++++++++++++++++++++++++++++++ src/api/author/controller.php | 15 +++++++++++++++ src/api/author/model.php | 14 ++++++++++++++ src/api/author/router.php | 15 +++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 src/api/author/Author.php create mode 100644 src/api/author/controller.php create mode 100644 src/api/author/model.php create mode 100644 src/api/author/router.php diff --git a/index.php b/index.php index 254ea96..358fa2a 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,7 @@ group('/category', CategoryRouter::class); +$app->group('/author', AuthorRouter::class); /** Application Execute or Run */ $app->run(); \ No newline at end of file diff --git a/src/api/author/Author.php b/src/api/author/Author.php new file mode 100644 index 0000000..641670d --- /dev/null +++ b/src/api/author/Author.php @@ -0,0 +1,32 @@ +result = $this->model->fetch(); + } + + /** Use core controller functions */ + use CoreController; +} \ No newline at end of file diff --git a/src/api/author/model.php b/src/api/author/model.php new file mode 100644 index 0000000..2165a9a --- /dev/null +++ b/src/api/author/model.php @@ -0,0 +1,14 @@ +get('/', [Controller::class, "findAll"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); + $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); + $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); + $router->post('/', [Controller::class, "create"]); + $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); + } +} \ No newline at end of file From 8a8ca79c8638131b02626e00f5344be52abcccf9 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 20 Jan 2025 18:48:25 +0530 Subject: [PATCH 31/80] perf: bcrypt password value --- src/trait/Model.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/trait/Model.php b/src/trait/Model.php index f80ee9d..6ef640c 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -26,15 +26,28 @@ public function delete(string $id) { /** Update data by id */ public function update(array $args, string $id) { $result = 0; - foreach ($args as $key => $value) - $result = $this->queryBuilder->update($this->table, "q")->set("q.$key", ":$key" )->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); + + foreach ($args as $key => $value): + if ($key == 'password') + $value = password_hash($value, PASSWORD_BCRYPT); + + $result = $this->queryBuilder->update($this->table, "q")->set("q.$key", ":$key")->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); + endforeach; + return $result; } /** Insert data */ public function insert(array $args) { $data = new $this->table; - foreach ($args as $key => $value) $data->__set($key, $value); + + foreach ($args as $key => $value): + if ($key == 'password') + $value = password_hash($value, PASSWORD_BCRYPT); + + $data->__set($key, $value); + endforeach; + $this->entityManager->persist($data); $this->entityManager->flush(); } From 3cc9c33991dcdf3c8c3530049bf75abdbe4f2736 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 21 Jan 2025 17:14:15 +0530 Subject: [PATCH 32/80] pref: model all query functions --- src/api/author/controller.php | 2 +- src/api/category/controller.php | 2 +- src/trait/Controller.php | 6 +++-- src/trait/Model.php | 42 +++++++++++++++++++-------------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/api/author/controller.php b/src/api/author/controller.php index cb28e0c..16bcfd7 100644 --- a/src/api/author/controller.php +++ b/src/api/author/controller.php @@ -7,7 +7,7 @@ class Controller { function __construct(private Model $model) { - $this->result = $this->model->fetch(); + $this->result = $this->model->fetchAll(); } /** Use core controller functions */ diff --git a/src/api/category/controller.php b/src/api/category/controller.php index 87c6cbd..45b27ec 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -7,7 +7,7 @@ class Controller { function __construct(private Model $model) { - $this->result = $this->model->fetch(); + $this->result = $this->model->fetchAll(); } /** Use core controller functions */ diff --git a/src/trait/Controller.php b/src/trait/Controller.php index 5c7eefd..28f68a3 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -31,8 +31,10 @@ public function findAll($req, $res) { /** Fetch data by Column */ public function findByColumn($req, $res, $args) { + foreach ($args as $key => $value) $result = array_filter($this->result, fn($item) => $item[$key] == $args[$key]); + checkNull($result, $req); return response($req, $res, args: new Response(data: [...$result])); } @@ -46,8 +48,8 @@ public function deleteById($req, $res, $args) { /** Insert data */ public function create($req, $res, $args) { - $this->model->insert($req->getParsedBody()); - return response($req, $res, new Response(message: "This item has been successfully added.")); + $result = $this->model->insert($req->getParsedBody()); + return response($req, $res, new Response(message: "This item has been successfully added.", data: $result)); } /** Update by id */ diff --git a/src/trait/Model.php b/src/trait/Model.php index 6ef640c..21a05f9 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -7,48 +7,54 @@ trait Model { /** Variables Declaration */ - private $queryBuilder; + private $repository; public function __construct(private EntityManager $entityManager) { - $this->queryBuilder = $entityManager->createQueryBuilder(); + $this->repository = $entityManager->getRepository($this->table); } /** Fetch all data */ - public function fetch(): array { - return $this->queryBuilder->select('q')->from($this->table, 'q')->getQuery()->getArrayResult(); + public function fetchAll(): array { + return $this->repository->findAll(); + } + + /** Fetch all data by id */ + public function fetchById($id): object { + return $this->repository->find($id); } /** Delete data by id */ - public function delete(string $id) { - return $this->queryBuilder->delete($this->table, 'q')->where("q.id = :id")->setParameter(':id', $id)->getQuery()->execute(); + public function delete(string $id) { + $data = $this->repository->find($id); + $this->entityManager->remove($data); + $this->entityManager->flush(); + + return $data; } /** Update data by id */ public function update(array $args, string $id) { - $result = 0; + $data = $this->repository->find($id); - foreach ($args as $key => $value): - if ($key == 'password') - $value = password_hash($value, PASSWORD_BCRYPT); + foreach ($args as $key => $value) + $data->__set($key, $value); - $result = $this->queryBuilder->update($this->table, "q")->set("q.$key", ":$key")->where("q.id = :id")->setParameter(":$key", $value)->setParameter('id', $id)->getQuery()->execute(); - endforeach; + $this->entityManager->merge($data); + $this->entityManager->flush(); - return $result; + return $data; } /** Insert data */ public function insert(array $args) { $data = new $this->table; - foreach ($args as $key => $value): - if ($key == 'password') - $value = password_hash($value, PASSWORD_BCRYPT); - + foreach ($args as $key => $value) $data->__set($key, $value); - endforeach; $this->entityManager->persist($data); $this->entityManager->flush(); + + return $data; } } \ No newline at end of file From 5526efd1bfafabbe9416c6202d4184e9bf8bf725 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 21 Jan 2025 17:23:51 +0530 Subject: [PATCH 33/80] perf: entity and password bcrypt --- src/api/author/Author.php | 27 +++++++++++++++++++++------ src/api/category/Category.php | 14 +++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/api/author/Author.php b/src/api/author/Author.php index 641670d..2862774 100644 --- a/src/api/author/Author.php +++ b/src/api/author/Author.php @@ -2,31 +2,46 @@ declare(strict_types=1); namespace RestJS\Api\Author; use Doctrine\ORM\Mapping as ORM; +use Doctrine\ORM\Event as Event; use RestJS\Trait\GetterAndSetter; #[ORM\Entity] #[ORM\Table('author')] +#[ORM\HasLifecycleCallbacks] class Author { - // Get and Set methods + // Get and Set Methods use GetterAndSetter; #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] - private int $id; + public int $id; #[ORM\Column(unique: true)] - private string $name; + public string $name; #[ORM\Column(unique: true)] - private string $username; + public string $username; #[ORM\Column] private string $password; #[ORM\Column(name: "created_at", insertable: false, updatable: false)] - private string $createdAt; + public string $createdAt; #[ORM\Column(name: "updated_at", insertable: false, updatable: false)] - private string $updatedAt; + public string $updatedAt; + + #[ORM\PrePersist] + public function prePersist() { + $this->password = password_hash($this->password, PASSWORD_BCRYPT); + } + + #[ORM\PreUpdate] + public function preUpdate(Event\PreUpdateEventArgs $event) { + $passwordModify = $event->hasChangedField('password') ?? false; + + if ($passwordModify) + $this->password = password_hash($this->password, PASSWORD_BCRYPT); + } } \ No newline at end of file diff --git a/src/api/category/Category.php b/src/api/category/Category.php index 6a996b5..cc2e601 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -13,23 +13,23 @@ class Category { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] - private int $id; + public int $id; #[ORM\Column(unique: true)] - private string $title; + public string $title; #[ORM\Column(unique: true)] - private string $slug; + public string $slug; #[ORM\Column] - private string $description; + public string $description; #[ORM\Column(name: "author_id")] - private int $authorId; + public int $authorId; #[ORM\Column(name: "created_at", insertable: false, updatable: false)] - private string $createdAt; + public string $createdAt; #[ORM\Column(name: "updated_at", insertable: false, updatable: false)] - private string $updatedAt; + public string $updatedAt; } \ No newline at end of file From ed2ce1bf9460ea8df9dc45f3265352d3a8df38f6 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 21 Jan 2025 17:44:14 +0530 Subject: [PATCH 34/80] fix: get query filter entity column --- src/trait/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trait/Controller.php b/src/trait/Controller.php index 28f68a3..e501082 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -23,7 +23,7 @@ public function findAll($req, $res) { $filter = explode(",", $filter); foreach ($this->result as $item) - array_push($result, array_intersect_key($item, array_flip($filter))); + array_push($result, array_intersect_key((array) $item, array_flip($filter))); endif; return response($req, $res, new Response(data: $result)); From e97386739c52c6d371ea5e002e10ac02d1b174f7 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 21 Jan 2025 22:23:27 +0530 Subject: [PATCH 35/80] build(deps): added firebase/php-jwt --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7d907b6..aa42c2e 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "doctrine-orm", "symfony-cache", "php-di-container", - "middlewares-trailing-slash" + "middlewares-trailing-slash", + "firebase-php-jwt" ], "homepage": "https://github.com/fullstackondemand/php-rest-api", "license": "MIT", @@ -43,6 +44,7 @@ "doctrine/orm": "^2.20", "php-di/php-di": "^7.0", "symfony/cache": "^6.0", - "middlewares/trailing-slash": "^2.0" + "middlewares/trailing-slash": "^2.0", + "firebase/php-jwt": "^6.10" } } From 27320077b10e775049bbb9489612f3dd59e05ca8 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 01:07:23 +0530 Subject: [PATCH 36/80] chore: login and logout functions --- .env-sample | 6 ++++- index.php | 3 +++ src/api/author/Author.php | 14 ++++++++++++ src/api/author/controller.php | 41 +++++++++++++++++++++++++++++++++++ src/trait/Model.php | 7 +++++- 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/.env-sample b/.env-sample index 41dc401..cc8f661 100644 --- a/.env-sample +++ b/.env-sample @@ -6,4 +6,8 @@ PASSWORD = # Broswer Variables CORS_ORIGIN = * -SHOW_ERROR = 1 or 0 \ No newline at end of file +SHOW_ERROR = 1 or 0 + +# Access Token Variables +ACCESS_TOKEN_SECRET = +ACCESS_TOKEN_EXPIRY = \ No newline at end of file diff --git a/index.php b/index.php index 358fa2a..e93c235 100644 --- a/index.php +++ b/index.php @@ -2,6 +2,7 @@ use RestJS\App; use RestJS\Api\Category\Router as CategoryRouter; use RestJS\Api\Author\Router as AuthorRouter; +use RestJS\Api\Author\Controller as AuthorController; require __DIR__ . '/vendor/autoload.php'; @@ -9,6 +10,8 @@ $app = App::create(__DIR__); /** Routers */ +$app->get('/login/', [AuthorController::class, 'login']); +$app->get('/logout/', [AuthorController::class, 'logout']); $app->group('/category', CategoryRouter::class); $app->group('/author', AuthorRouter::class); diff --git a/src/api/author/Author.php b/src/api/author/Author.php index 2862774..3bf7efc 100644 --- a/src/api/author/Author.php +++ b/src/api/author/Author.php @@ -3,6 +3,7 @@ namespace RestJS\Api\Author; use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Event as Event; +use Firebase\JWT\JWT; use RestJS\Trait\GetterAndSetter; #[ORM\Entity] @@ -44,4 +45,17 @@ public function preUpdate(Event\PreUpdateEventArgs $event) { if ($passwordModify) $this->password = password_hash($this->password, PASSWORD_BCRYPT); } + + /** Verify Password Function */ + public function verifyPassword($password) { + return password_verify($password, $this->password); + } + + /** Generate Access Token Function */ + public function generateAccessToken() { + return JWT::encode([ + 'id' => $this->id, + 'username' => $this->username + ], $_ENV['ACCESS_TOKEN_SECRET'], 'HS256'); + } } \ No newline at end of file diff --git a/src/api/author/controller.php b/src/api/author/controller.php index 16bcfd7..ebec406 100644 --- a/src/api/author/controller.php +++ b/src/api/author/controller.php @@ -1,8 +1,12 @@ getParsedBody() ?? []); + + // Check login credentials + if (!$username || !$password) + throw new HttpBadRequestException($req, "Username or password is required."); + + /** Verify Author Detail */ + $author = $this->model->fetchBy(['username' => $username])[0]; + $isValidPassword = $author->verifyPassword($password); + + if (!$isValidPassword) + throw new HttpUnauthorizedException($req, 'Invalid user credentials'); + + $accessToken = $author->generateAccessToken(); + + // Add Authorization Cookies + setcookie('SSID', $accessToken, time() + 84600 * intval($_ENV['ACCESS_TOKEN_EXPIRY']), secure: true, httponly: true); + + return response($req, $res, new Response(message: "Author logged in successfully.", data: ['author' => ['accessToken' => $accessToken]])); + } + + /** Author Logout Function */ + public function logout($req, $res) { + + // Remove Authorization Cookies + setcookie('SSID', '', time() - 1, secure: true, httponly: true); + return response($req, $res, new Response(message: "Author logged out successfully.")); + } } \ No newline at end of file diff --git a/src/trait/Model.php b/src/trait/Model.php index 21a05f9..b9db44b 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -18,7 +18,12 @@ public function fetchAll(): array { return $this->repository->findAll(); } - /** Fetch all data by id */ + /** Fetch data by conditional */ + public function fetchBy($array): array { + return $this->repository->findBy($array); + } + + /** Fetch data by id */ public function fetchById($id): object { return $this->repository->find($id); } From 236d857af35fcd5fb69bdcaf30286336ae97fe4c Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 01:08:56 +0530 Subject: [PATCH 37/80] chore: authorization middleware --- index.php | 5 +++-- src/middleware/authorization.php | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/middleware/authorization.php diff --git a/index.php b/index.php index e93c235..4a7644c 100644 --- a/index.php +++ b/index.php @@ -3,6 +3,7 @@ use RestJS\Api\Category\Router as CategoryRouter; use RestJS\Api\Author\Router as AuthorRouter; use RestJS\Api\Author\Controller as AuthorController; +use RestJS\Middleware\Authorization; require __DIR__ . '/vendor/autoload.php'; @@ -12,8 +13,8 @@ /** Routers */ $app->get('/login/', [AuthorController::class, 'login']); $app->get('/logout/', [AuthorController::class, 'logout']); -$app->group('/category', CategoryRouter::class); -$app->group('/author', AuthorRouter::class); +$app->group('/category', CategoryRouter::class)->add(Authorization::class); +$app->group('/author', AuthorRouter::class)->add(Authorization::class); /** Application Execute or Run */ $app->run(); \ No newline at end of file diff --git a/src/middleware/authorization.php b/src/middleware/authorization.php new file mode 100644 index 0000000..4face50 --- /dev/null +++ b/src/middleware/authorization.php @@ -0,0 +1,37 @@ +getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; + + if (!$token) + throw new HttpUnauthorizedException($req, 'Unauthorized request'); + + /** Decode Json Web Token */ + $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); + + /** Get Author Detail */ + $author = $this->author->fetchById($decodedToken['id']); + + if (!$author) + throw new HttpUnauthorizedException($req, "Invalid access token"); + + $req->author = $author; + + return $handler->handle($req); + } +} \ No newline at end of file From ef05cfbc313c0066ccbee488367487f5dc451c33 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 01:17:46 +0530 Subject: [PATCH 38/80] fix: logout function after login --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 4a7644c..b413ef2 100644 --- a/index.php +++ b/index.php @@ -12,7 +12,7 @@ /** Routers */ $app->get('/login/', [AuthorController::class, 'login']); -$app->get('/logout/', [AuthorController::class, 'logout']); +$app->get('/logout/', [AuthorController::class, 'logout'])->add(Authorization::class); $app->group('/category', CategoryRouter::class)->add(Authorization::class); $app->group('/author', AuthorRouter::class)->add(Authorization::class); From 60094d7841cdf6faa9658ac54e3579aa48cbf352 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 08:51:18 +0530 Subject: [PATCH 39/80] refactor: author to user entity --- index.php | 10 +++++----- src/api/{author/Author.php => user/User.php} | 6 +++--- src/api/{author => user}/controller.php | 16 ++++++++-------- src/api/{author => user}/model.php | 6 +++--- src/api/{author => user}/router.php | 4 ++-- src/middleware/authorization.php | 10 +++++----- 6 files changed, 26 insertions(+), 26 deletions(-) rename src/api/{author/Author.php => user/User.php} (95%) rename src/api/{author => user}/controller.php (71%) rename src/api/{author => user}/model.php (64%) rename src/api/{author => user}/router.php (89%) diff --git a/index.php b/index.php index b413ef2..dabf886 100644 --- a/index.php +++ b/index.php @@ -1,8 +1,8 @@ get('/login/', [AuthorController::class, 'login']); -$app->get('/logout/', [AuthorController::class, 'logout'])->add(Authorization::class); +$app->get('/login/', [UserController::class, 'login']); +$app->get('/logout/', [UserController::class, 'logout'])->add(Authorization::class); $app->group('/category', CategoryRouter::class)->add(Authorization::class); -$app->group('/author', AuthorRouter::class)->add(Authorization::class); +$app->group('/author', UserRouter::class)->add(Authorization::class); /** Application Execute or Run */ $app->run(); \ No newline at end of file diff --git a/src/api/author/Author.php b/src/api/user/User.php similarity index 95% rename from src/api/author/Author.php rename to src/api/user/User.php index 3bf7efc..b800497 100644 --- a/src/api/author/Author.php +++ b/src/api/user/User.php @@ -1,15 +1,15 @@ model->fetchBy(['username' => $username])[0]; - $isValidPassword = $author->verifyPassword($password); + /** Verify User Detail */ + $user = $this->model->fetchBy(['username' => $username])[0]; + $isValidPassword = $user->verifyPassword($password); if (!$isValidPassword) throw new HttpUnauthorizedException($req, 'Invalid user credentials'); - $accessToken = $author->generateAccessToken(); + $accessToken = $user->generateAccessToken(); // Add Authorization Cookies setcookie('SSID', $accessToken, time() + 84600 * intval($_ENV['ACCESS_TOKEN_EXPIRY']), secure: true, httponly: true); - return response($req, $res, new Response(message: "Author logged in successfully.", data: ['author' => ['accessToken' => $accessToken]])); + return response($req, $res, new Response(message: "User logged in successfully.", data: ['user' => ['accessToken' => $accessToken]])); } /** Author Logout Function */ @@ -51,6 +51,6 @@ public function logout($req, $res) { // Remove Authorization Cookies setcookie('SSID', '', time() - 1, secure: true, httponly: true); - return response($req, $res, new Response(message: "Author logged out successfully.")); + return response($req, $res, new Response(message: "User logged out successfully.")); } } \ No newline at end of file diff --git a/src/api/author/model.php b/src/api/user/model.php similarity index 64% rename from src/api/author/model.php rename to src/api/user/model.php index 2165a9a..129b9c8 100644 --- a/src/api/author/model.php +++ b/src/api/user/model.php @@ -1,13 +1,13 @@ getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; @@ -25,12 +25,12 @@ function process(Request $req, RequestHandler $handler): ResponseInterface { $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); /** Get Author Detail */ - $author = $this->author->fetchById($decodedToken['id']); + $user = $this->user->fetchById($decodedToken['id']); - if (!$author) + if (!$user) throw new HttpUnauthorizedException($req, "Invalid access token"); - $req->author = $author; + $req->user = $user; return $handler->handle($req); } From 0ad914c63d02563f7058acf1005c636d143ee769 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 09:34:24 +0530 Subject: [PATCH 40/80] perf: error handle for jwt token decode --- index.php | 2 +- src/middleware/authorization.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index dabf886..130dc79 100644 --- a/index.php +++ b/index.php @@ -14,7 +14,7 @@ $app->get('/login/', [UserController::class, 'login']); $app->get('/logout/', [UserController::class, 'logout'])->add(Authorization::class); $app->group('/category', CategoryRouter::class)->add(Authorization::class); -$app->group('/author', UserRouter::class)->add(Authorization::class); +$app->group('/user', UserRouter::class)->add(Authorization::class); /** Application Execute or Run */ $app->run(); \ No newline at end of file diff --git a/src/middleware/authorization.php b/src/middleware/authorization.php index 885758b..8731228 100644 --- a/src/middleware/authorization.php +++ b/src/middleware/authorization.php @@ -18,11 +18,19 @@ function __construct(private User $user) {} function process(Request $req, RequestHandler $handler): ResponseInterface { $token = $_COOKIE['SSID'] ?? str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; - if (!$token) + if (!$token) throw new HttpUnauthorizedException($req, 'Unauthorized request'); /** Decode Json Web Token */ - $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); + try { + $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); + } + catch (\Exception $e) { + $decodedToken = null; + } + + if (!$decodedToken) + throw new HttpUnauthorizedException($req, "Invalid access token"); /** Get Author Detail */ $user = $this->user->fetchById($decodedToken['id']); From 63d8de88e7267a82e601a32ee364d930e0408a8e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 09:46:57 +0530 Subject: [PATCH 41/80] fix: filter column key value --- src/api/user/controller.php | 4 ++-- src/trait/Controller.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/user/controller.php b/src/api/user/controller.php index af65748..fd421a3 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -17,7 +17,7 @@ function __construct(private Model $model) { /** Use core controller functions */ use CoreController; - /** Author Login Function */ + /** User Login Function */ public function login($req, $res) { /** Variables Declaration */ @@ -46,7 +46,7 @@ public function login($req, $res) { return response($req, $res, new Response(message: "User logged in successfully.", data: ['user' => ['accessToken' => $accessToken]])); } - /** Author Logout Function */ + /** User Logout Function */ public function logout($req, $res) { // Remove Authorization Cookies diff --git a/src/trait/Controller.php b/src/trait/Controller.php index e501082..a2cf21d 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -33,7 +33,7 @@ public function findAll($req, $res) { public function findByColumn($req, $res, $args) { foreach ($args as $key => $value) - $result = array_filter($this->result, fn($item) => $item[$key] == $args[$key]); + $result = array_filter($this->result, fn($item) => $item->$key == $args[$key]); checkNull($result, $req); return response($req, $res, args: new Response(data: [...$result])); From 564af39243be46c0a03fc77614a7ea56f05d620e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 22 Jan 2025 11:29:55 +0530 Subject: [PATCH 42/80] chore: trait for authorization functions --- src/api/user/controller.php | 45 +++--------------------------- src/middleware/authorization.php | 2 +- src/trait/Authorization.php | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 42 deletions(-) create mode 100644 src/trait/Authorization.php diff --git a/src/api/user/controller.php b/src/api/user/controller.php index fd421a3..aad518e 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -1,12 +1,9 @@ getParsedBody() ?? []); - - // Check login credentials - if (!$username || !$password) - throw new HttpBadRequestException($req, "Username or password is required."); - - /** Verify User Detail */ - $user = $this->model->fetchBy(['username' => $username])[0]; - $isValidPassword = $user->verifyPassword($password); - - if (!$isValidPassword) - throw new HttpUnauthorizedException($req, 'Invalid user credentials'); - - $accessToken = $user->generateAccessToken(); - - // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 84600 * intval($_ENV['ACCESS_TOKEN_EXPIRY']), secure: true, httponly: true); - - return response($req, $res, new Response(message: "User logged in successfully.", data: ['user' => ['accessToken' => $accessToken]])); - } - - /** User Logout Function */ - public function logout($req, $res) { - - // Remove Authorization Cookies - setcookie('SSID', '', time() - 1, secure: true, httponly: true); - return response($req, $res, new Response(message: "User logged out successfully.")); - } + + /** Use authorization controller functions */ + use AuthController; } \ No newline at end of file diff --git a/src/middleware/authorization.php b/src/middleware/authorization.php index 8731228..73a128d 100644 --- a/src/middleware/authorization.php +++ b/src/middleware/authorization.php @@ -32,7 +32,7 @@ function process(Request $req, RequestHandler $handler): ResponseInterface { if (!$decodedToken) throw new HttpUnauthorizedException($req, "Invalid access token"); - /** Get Author Detail */ + /** Get User Detail */ $user = $this->user->fetchById($decodedToken['id']); if (!$user) diff --git a/src/trait/Authorization.php b/src/trait/Authorization.php new file mode 100644 index 0000000..627523c --- /dev/null +++ b/src/trait/Authorization.php @@ -0,0 +1,48 @@ +getParsedBody() ?? []); + + // Check login credentials + if (!$username || !$password) + throw new HttpBadRequestException($req, "Username or password is required."); + + /** Verify Entity Detail */ + $user = $this->model->fetchBy(['username' => $username])[0]; + $isValidPassword = $user->verifyPassword($password); + + if (!$isValidPassword) + throw new HttpUnauthorizedException($req, 'Invalid user credentials'); + + $accessToken = $user->generateAccessToken(); + + // Add Authorization Cookies + setcookie('SSID', $accessToken, time() + 84600 * intval($_ENV['ACCESS_TOKEN_EXPIRY']), path: '/', secure: true, httponly: true); + + return response($req, $res, new Response(message: "User logged in successfully.", data: ['accessToken' => $accessToken])); + } + + /** User Logout Function */ + public function logout($req, $res) { + + // Remove Authorization Cookies + setcookie('SSID', '', time() - 100, path: '/', secure: true, httponly: true); + return response($req, $res, new Response(message: "User logged out successfully.")); + } +} \ No newline at end of file From 900ca1da80c594dd5371f51d761f1db6adc5227b Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 24 Jan 2025 09:45:43 +0530 Subject: [PATCH 43/80] chore: change comments and variables name --- index.php | 6 ++-- src/api/category/Category.php | 3 +- src/api/category/controller.php | 5 +-- src/api/category/model.php | 7 ++-- src/api/category/router.php | 8 +++-- src/api/user/User.php | 3 +- src/api/user/controller.php | 7 ++-- src/api/user/model.php | 7 ++-- src/api/user/router.php | 10 +++--- src/app.php | 10 +++--- src/database.php | 6 +++- src/function.php | 7 ++-- src/middleware/authorization.php | 11 +++--- src/trait/Authorization.php | 19 ++++++---- src/trait/Controller.php | 59 +++++++++++++++++--------------- src/trait/GetterAndSetter.php | 10 +++--- src/trait/Model.php | 21 ++++++------ 17 files changed, 116 insertions(+), 83 deletions(-) diff --git a/index.php b/index.php index 130dc79..0f8a856 100644 --- a/index.php +++ b/index.php @@ -10,11 +10,11 @@ /** Create Application */ $app = App::create(__DIR__); -/** Routers */ -$app->get('/login/', [UserController::class, 'login']); +// Routers +$app->post('/login/', [UserController::class, 'login']); $app->get('/logout/', [UserController::class, 'logout'])->add(Authorization::class); $app->group('/category', CategoryRouter::class)->add(Authorization::class); $app->group('/user', UserRouter::class)->add(Authorization::class); -/** Application Execute or Run */ +// Application Execute or Run $app->run(); \ No newline at end of file diff --git a/src/api/category/Category.php b/src/api/category/Category.php index cc2e601..0315be2 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -1,6 +1,7 @@ result = $this->model->fetchAll(); + $this->data = $this->model->findAll(); } - /** Use core controller functions */ + // Trait Controller use CoreController; } \ No newline at end of file diff --git a/src/api/category/model.php b/src/api/category/model.php index 75c1f57..ff37b92 100644 --- a/src/api/category/model.php +++ b/src/api/category/model.php @@ -1,14 +1,15 @@ get('/', [Controller::class, "findAll"]); $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); - $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); - $router->post('/', [Controller::class, "create"]); - $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); + $router->put('/{id:[0-9]+}/', [Controller::class, "update"]); + $router->post('/', [Controller::class, "insert"]); + $router->delete('/{id:[0-9]+}/', [Controller::class, "delete"]); } } \ No newline at end of file diff --git a/src/api/user/User.php b/src/api/user/User.php index b800497..14d7b7e 100644 --- a/src/api/user/User.php +++ b/src/api/user/User.php @@ -1,6 +1,7 @@ result = $this->model->fetchAll(); + $this->data = $this->model->findAll(); } - /** Use core controller functions */ + // Trait Controller use CoreController; - /** Use authorization controller functions */ + // Trait Authorization use AuthController; } \ No newline at end of file diff --git a/src/api/user/model.php b/src/api/user/model.php index 129b9c8..2b721b3 100644 --- a/src/api/user/model.php +++ b/src/api/user/model.php @@ -1,14 +1,15 @@ get('/', [Controller::class, "findAll"]); $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); - $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); - $router->put('/{id:[0-9]+}/', [Controller::class, "updateById"]); - $router->post('/', [Controller::class, "create"]); - $router->delete('/{id:[0-9]+}/', [Controller::class, "deleteById"]); + $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); + $router->put('/{id:[0-9]+}/', [Controller::class, "update"]); + $router->post('/', [Controller::class, "insert"]); + $router->delete('/{id:[0-9]+}/', [Controller::class, "delete"]); } } \ No newline at end of file diff --git a/src/app.php b/src/app.php index 8bc7394..bb2f1ec 100644 --- a/src/app.php +++ b/src/app.php @@ -1,10 +1,12 @@ load(); - /** Headers Variables */ + // Header All Allows header("Access-Control-Allow-Origin: {$_ENV['CORS_ORIGIN']}"); header("Content-Type: application/json; charset=UTF-8"); header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE"); header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); - /** Database Connection Established */ + // Database Connection Database::connection(); - /** Server Application Initialize */ + /** Server Application */ $app = AppFactory::create(); - /** Middlewares */ + // Core Middlewares $app->setBasePath("/api"); // set base path $app->addBodyParsingMiddleware(); // It is used to get json and form body data $app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error diff --git a/src/database.php b/src/database.php index ab8cccb..1fd40eb 100644 --- a/src/database.php +++ b/src/database.php @@ -1,13 +1,17 @@ getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; if (!$token) throw new HttpUnauthorizedException($req, 'Unauthorized request'); - /** Decode Json Web Token */ try { + /** Decode Json Web Token */ $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); } catch (\Exception $e) { @@ -32,8 +35,8 @@ function process(Request $req, RequestHandler $handler): ResponseInterface { if (!$decodedToken) throw new HttpUnauthorizedException($req, "Invalid access token"); - /** Get User Detail */ - $user = $this->user->fetchById($decodedToken['id']); + /** Check User Entity */ + $user = $this->user->findById($decodedToken['id']); if (!$user) throw new HttpUnauthorizedException($req, "Invalid access token"); diff --git a/src/trait/Authorization.php b/src/trait/Authorization.php index 627523c..9b2b61f 100644 --- a/src/trait/Authorization.php +++ b/src/trait/Authorization.php @@ -1,6 +1,7 @@ getParsedBody() ?? []); - // Check login credentials + // Check Login Credentials if (!$username || !$password) throw new HttpBadRequestException($req, "Username or password is required."); - /** Verify Entity Detail */ - $user = $this->model->fetchBy(['username' => $username])[0]; + /** Check User Entity */ + $user = $this->model->findBy(['username' => $username])[0]; + + /** Verify User Password */ $isValidPassword = $user->verifyPassword($password); if (!$isValidPassword) throw new HttpUnauthorizedException($req, 'Invalid user credentials'); + /** Generated Access Token */ $accessToken = $user->generateAccessToken(); // Add Authorization Cookies @@ -38,11 +44,12 @@ public function login($req, $res) { return response($req, $res, new Response(message: "User logged in successfully.", data: ['accessToken' => $accessToken])); } - /** User Logout Function */ + /** Logout Function */ public function logout($req, $res) { // Remove Authorization Cookies setcookie('SSID', '', time() - 100, path: '/', secure: true, httponly: true); + return response($req, $res, new Response(message: "User logged out successfully.")); } } \ No newline at end of file diff --git a/src/trait/Controller.php b/src/trait/Controller.php index a2cf21d..e7c6cbb 100644 --- a/src/trait/Controller.php +++ b/src/trait/Controller.php @@ -1,61 +1,64 @@ result; + /** Filter Column Query Params */ $filter = $req->getQueryParams()['filter'] ?? null; + + /** Filter Data */ + $data = $this->data; - /** Selected column fetch all data */ + // Selected Column Fetch All Data if ($filter): - $result = []; + $data = []; $filter = explode(",", $filter); - foreach ($this->result as $item) - array_push($result, array_intersect_key((array) $item, array_flip($filter))); + foreach ($this->data as $item) + array_push($data, array_intersect_key((array) $item, array_flip($filter))); endif; - return response($req, $res, new Response(data: $result)); + return response($req, $res, new Response(data: $data)); } - /** Fetch data by Column */ + /** Find Data by Column */ public function findByColumn($req, $res, $args) { foreach ($args as $key => $value) - $result = array_filter($this->result, fn($item) => $item->$key == $args[$key]); + $data = array_filter($this->data, fn($item) => $item->$key == $args[$key]); - checkNull($result, $req); - return response($req, $res, args: new Response(data: [...$result])); + checkNull($data, $req); + return response($req, $res, args: new Response(data: [...$data])); } - /** Delete data by id */ - public function deleteById($req, $res, $args) { - $result = $this->model->delete($args['id']); - checkNull($result, $req); - return response($req, $res, new Response(message: "This item has been successfully removed.", data: $result)); + /** Delete Data by Id */ + public function delete($req, $res, $args) { + $data = $this->model->delete($args['id']); + checkNull($data, $req); + return response($req, $res, new Response(message: "This item has been successfully removed.", data: $data)); } - /** Insert data */ - public function create($req, $res, $args) { - $result = $this->model->insert($req->getParsedBody()); - return response($req, $res, new Response(message: "This item has been successfully added.", data: $result)); + /** Insert Data */ + public function insert($req, $res, $args) { + $data = $this->model->insert($req->getParsedBody()); + return response($req, $res, new Response(message: "This item has been successfully added.", data: $data)); } - /** Update by id */ - public function updateById($req, $res, $args) { - $result = $this->model->update($req->getParsedBody(), $args["id"]); - checkNull($result, $req); - return response($req, $res, new Response(message: "This item has been successfully updated.", data: $result)); + /** Update by Id */ + public function update($req, $res, $args) { + $data = $this->model->update($req->getParsedBody(), $args["id"]); + checkNull($data, $req); + return response($req, $res, new Response(message: "This item has been successfully updated.", data: $data)); } } \ No newline at end of file diff --git a/src/trait/GetterAndSetter.php b/src/trait/GetterAndSetter.php index e015b94..736adae 100644 --- a/src/trait/GetterAndSetter.php +++ b/src/trait/GetterAndSetter.php @@ -5,11 +5,13 @@ /** Getter and Setter Functions */ trait GetterAndSetter { - public function __get($name) { - return $this->$name; + /** Get Value by Column */ + public function __get($key) { + return $this->$key; } - public function __set($name, $value) { - $this->$name = $value; + /** Set Value by Column */ + public function __set($key, $value) { + $this->$key = $value; } } \ No newline at end of file diff --git a/src/trait/Model.php b/src/trait/Model.php index b9db44b..4cdc62f 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -1,34 +1,35 @@ repository = $entityManager->getRepository($this->table); } - /** Fetch all data */ - public function fetchAll(): array { + /** Find All Data */ + public function findAll(): array { return $this->repository->findAll(); } - /** Fetch data by conditional */ - public function fetchBy($array): array { + /** Find Data by Conditional */ + public function findBy($array): array { return $this->repository->findBy($array); } - /** Fetch data by id */ - public function fetchById($id): object { + /** Find Data by Id */ + public function findById($id): object { return $this->repository->find($id); } - /** Delete data by id */ + /** Delete Data by Id */ public function delete(string $id) { $data = $this->repository->find($id); $this->entityManager->remove($data); @@ -37,7 +38,7 @@ public function delete(string $id) { return $data; } - /** Update data by id */ + /** Update Data by Id */ public function update(array $args, string $id) { $data = $this->repository->find($id); @@ -50,7 +51,7 @@ public function update(array $args, string $id) { return $data; } - /** Insert data */ + /** Insert Data */ public function insert(array $args) { $data = new $this->table; From b904463615b5e604a74de2897aedc2ec7b197d8e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 24 Jan 2025 13:08:29 +0530 Subject: [PATCH 44/80] fix: error handle check user entity --- src/function.php | 9 +++++++++ src/trait/Authorization.php | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/function.php b/src/function.php index e2dc028..0d5d4be 100644 --- a/src/function.php +++ b/src/function.php @@ -15,4 +15,13 @@ function response(Request $req, Response $res, mixed $args) { /** Check Null Value Function */ function checkNull($value, $req) { !boolval($value) && throw new HttpBadRequestException($req, "Something went wrong..."); +} + +/** Callback Error Handler Function */ +function errorHandler($callback) { + try { + return $callback; + } catch (\Exception $e) { + return null; + } } \ No newline at end of file diff --git a/src/trait/Authorization.php b/src/trait/Authorization.php index 9b2b61f..0f9f36b 100644 --- a/src/trait/Authorization.php +++ b/src/trait/Authorization.php @@ -3,6 +3,7 @@ namespace RestJS\Trait; use RestJS\Class\Response; +use function RestJS\errorHandler; use function RestJS\response; use Slim\Exception\HttpBadRequestException; use Slim\Exception\HttpUnauthorizedException; @@ -27,8 +28,13 @@ public function login($req, $res) { throw new HttpBadRequestException($req, "Username or password is required."); /** Check User Entity */ - $user = $this->model->findBy(['username' => $username])[0]; + $user = errorHandler($this->model->findBy(['username' => $username])); + if (!$user) + throw new HttpUnauthorizedException($req, 'Invalid user credentials'); + + $user = $user[0]; + /** Verify User Password */ $isValidPassword = $user->verifyPassword($password); From 68e7e2fe2ba2ae5e0520be0218bfc62d569df36a Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 24 Jan 2025 13:25:00 +0530 Subject: [PATCH 45/80] fix: auth user not found --- src/trait/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trait/Model.php b/src/trait/Model.php index 4cdc62f..a151bec 100644 --- a/src/trait/Model.php +++ b/src/trait/Model.php @@ -25,7 +25,7 @@ public function findBy($array): array { } /** Find Data by Id */ - public function findById($id): object { + public function findById($id) { return $this->repository->find($id); } From bab03b0c55adf86b38daf428a833441113607536 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 24 Jan 2025 19:24:40 +0530 Subject: [PATCH 46/80] chore: upload file middleware --- src/api/user/User.php | 6 +++++ src/api/user/router.php | 5 ++-- src/middleware/upload.php | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/middleware/upload.php diff --git a/src/api/user/User.php b/src/api/user/User.php index 14d7b7e..86ddc35 100644 --- a/src/api/user/User.php +++ b/src/api/user/User.php @@ -28,6 +28,12 @@ class User { #[ORM\Column] private string $password; + #[ORM\Column(nullable: true)] + public string $image; + + #[ORM\Column(nullable: true)] + public string $logo; + #[ORM\Column(name: "created_at", insertable: false, updatable: false)] public string $createdAt; diff --git a/src/api/user/router.php b/src/api/user/router.php index 26a99bf..e188248 100644 --- a/src/api/user/router.php +++ b/src/api/user/router.php @@ -4,14 +4,15 @@ use Slim\Routing\RouteCollectorProxy; use RestJS\Api\User\Controller; +use RestJS\Middleware\Upload; class Router { function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); - $router->put('/{id:[0-9]+}/', [Controller::class, "update"]); - $router->post('/', [Controller::class, "insert"]); + $router->put('/{id:[0-9]+}/', [Controller::class, "update"])->add(Upload::class); + $router->post('/', [Controller::class, "insert"])->add(Upload::class); $router->delete('/{id:[0-9]+}/', [Controller::class, "delete"]); } } \ No newline at end of file diff --git a/src/middleware/upload.php b/src/middleware/upload.php new file mode 100644 index 0000000..114600f --- /dev/null +++ b/src/middleware/upload.php @@ -0,0 +1,49 @@ +getUploadedFiles(); + + foreach ($uploadedFile as $key => $value): + + /** Upload File Path */ + $file = self::moveUploadedFile('../content/', $key, $value); + + /** Added Upload File Value with User Form Data */ + $req = $req->withParsedBody([...$req->getParsedBody(), $key => $file]); + endforeach; + + return $handler->handle($req); + } + + + /** File Upload to Server Function */ + function moveUploadedFile(string $directory, string $type, UploadedFileInterface $uploadedFile) { + + /** File Extension */ + $ext = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION); + + /** Upload File Name */ + $filename = strtoupper($type) . "_" . time() . ".{$ext}"; + + // Check and Create Target Directory + !is_dir($directory) && mkdir($directory); + + // File Upload to Server + $uploadedFile->moveTo($directory . $filename); + + return $directory . $filename; + } +} \ No newline at end of file From f7d71a5babf981779beea61d312371830b2489e1 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 12:36:10 +0530 Subject: [PATCH 47/80] pref: trait model convert abstract --- src/{trait/Model.php => abstract/model.php} | 13 ++++++++++--- src/api/category/model.php | 12 +++++------- src/api/user/model.php | 12 +++++------- 3 files changed, 20 insertions(+), 17 deletions(-) rename src/{trait/Model.php => abstract/model.php} (84%) diff --git a/src/trait/Model.php b/src/abstract/model.php similarity index 84% rename from src/trait/Model.php rename to src/abstract/model.php index a151bec..3a9932e 100644 --- a/src/trait/Model.php +++ b/src/abstract/model.php @@ -1,16 +1,23 @@ table = $this->setTable(); $this->repository = $entityManager->getRepository($this->table); } diff --git a/src/api/category/model.php b/src/api/category/model.php index ff37b92..5b62d67 100644 --- a/src/api/category/model.php +++ b/src/api/category/model.php @@ -2,14 +2,12 @@ declare(strict_types=1); namespace RestJS\Api\Category; -use RestJS\Trait\Model as CoreModel; +use RestJS\Abstract\Model as AbstractModel; use RestJS\Api\Category\Category; -class Model { +class Model extends AbstractModel { - /** Entity or Table Variable */ - private $table = Category::class; - - // Trait Model - use CoreModel; + protected function setTable() { + return Category::class; + } } \ No newline at end of file diff --git a/src/api/user/model.php b/src/api/user/model.php index 2b721b3..4297ef3 100644 --- a/src/api/user/model.php +++ b/src/api/user/model.php @@ -2,14 +2,12 @@ declare(strict_types=1); namespace RestJS\Api\User; -use RestJS\Trait\Model as CoreModel; +use RestJS\Abstract\Model as AbstractModel; use RestJS\Api\User\User; -class Model { +class Model extends AbstractModel { - /** Entity or Table Variable */ - private $table = User::class; - - // Trait Model - use CoreModel; + protected function setTable() { + return User::class; + } } \ No newline at end of file From fa916495f62acb93e863a8dd8ace6cf6bbd313c9 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 12:48:11 +0530 Subject: [PATCH 48/80] pref: trait getter and setter convert class --- src/api/category/Category.php | 7 ++----- src/api/user/User.php | 9 +++------ src/class/getterandsetter.php | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 src/class/getterandsetter.php diff --git a/src/api/category/Category.php b/src/api/category/Category.php index 0315be2..fd7b1b3 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -3,14 +3,11 @@ namespace RestJS\Api\Category; use Doctrine\ORM\Mapping as ORM; -use RestJS\Trait\GetterAndSetter; +use RestJS\Class\GetterAndSetter; #[ORM\Entity] #[ORM\Table('category')] -class Category { - - // Trait Getter and Setter - use GetterAndSetter; +class Category extends GetterAndSetter { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] diff --git a/src/api/user/User.php b/src/api/user/User.php index 86ddc35..93f38ea 100644 --- a/src/api/user/User.php +++ b/src/api/user/User.php @@ -5,15 +5,12 @@ use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Event as Event; use Firebase\JWT\JWT; -use RestJS\Trait\GetterAndSetter; +use RestJS\Class\GetterAndSetter; #[ORM\Entity] #[ORM\Table('user')] #[ORM\HasLifecycleCallbacks] -class User { - - // Trait Getter and Setter - use GetterAndSetter; +class User extends GetterAndSetter { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] @@ -26,7 +23,7 @@ class User { public string $username; #[ORM\Column] - private string $password; + protected string $password; #[ORM\Column(nullable: true)] public string $image; diff --git a/src/class/getterandsetter.php b/src/class/getterandsetter.php new file mode 100644 index 0000000..df426c9 --- /dev/null +++ b/src/class/getterandsetter.php @@ -0,0 +1,17 @@ +$key; + } + + /** Set Value by Column */ + public function __set($key, $value) { + $this->$key = $value; + } +} \ No newline at end of file From e9a8973164904c03a2db2191816b7f93faa4b9c7 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 13:05:06 +0530 Subject: [PATCH 49/80] pref: trait authorization convert abstract --- .../authorization.php} | 24 +++++++++++++------ src/api/user/controller.php | 14 ++++++----- 2 files changed, 25 insertions(+), 13 deletions(-) rename src/{trait/Authorization.php => abstract/authorization.php} (80%) diff --git a/src/trait/Authorization.php b/src/abstract/authorization.php similarity index 80% rename from src/trait/Authorization.php rename to src/abstract/authorization.php index 0f9f36b..78e4dab 100644 --- a/src/trait/Authorization.php +++ b/src/abstract/authorization.php @@ -1,6 +1,6 @@ model = $this->setModel(); + } /** Login Function */ - public function login($req, $res) { + public function login($req, $res) { /** Username Variable */ $username = false; @@ -21,7 +31,7 @@ public function login($req, $res) { $password = false; // Overide Username and Password Value - extract( $req->getParsedBody() ?? []); + extract($req->getParsedBody() ?? []); // Check Login Credentials if (!$username || !$password) @@ -34,7 +44,7 @@ public function login($req, $res) { throw new HttpUnauthorizedException($req, 'Invalid user credentials'); $user = $user[0]; - + /** Verify User Password */ $isValidPassword = $user->verifyPassword($password); @@ -43,7 +53,7 @@ public function login($req, $res) { /** Generated Access Token */ $accessToken = $user->generateAccessToken(); - + // Add Authorization Cookies setcookie('SSID', $accessToken, time() + 84600 * intval($_ENV['ACCESS_TOKEN_EXPIRY']), path: '/', secure: true, httponly: true); diff --git a/src/api/user/controller.php b/src/api/user/controller.php index dbd93c6..719b6c1 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -3,18 +3,20 @@ namespace RestJS\Api\User; use RestJS\Trait\Controller as CoreController; -use RestJS\Trait\Authorization as AuthController; +use RestJS\Abstract\Authorization as AuthController; use RestJS\Api\User\Model; -class Controller { +class Controller extends AuthController { function __construct(private Model $model) { $this->data = $this->model->findAll(); + parent::__construct(); + } + + protected function setModel() { + return $this->model; } // Trait Controller - use CoreController; - - // Trait Authorization - use AuthController; + use CoreController; } \ No newline at end of file From 948a00cee51ead19cba5e928b3b273ccd462e623 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 13:54:57 +0530 Subject: [PATCH 50/80] perf: trait controller convert abstract --- .../controller.php} | 36 +++++++++++++------ src/api/category/controller.php | 15 +++++--- src/trait/GetterAndSetter.php | 17 --------- 3 files changed, 35 insertions(+), 33 deletions(-) rename src/{trait/Controller.php => abstract/controller.php} (68%) delete mode 100644 src/trait/GetterAndSetter.php diff --git a/src/trait/Controller.php b/src/abstract/controller.php similarity index 68% rename from src/trait/Controller.php rename to src/abstract/controller.php index e7c6cbb..8dcf3aa 100644 --- a/src/trait/Controller.php +++ b/src/abstract/controller.php @@ -1,32 +1,46 @@ model = $this->setModel(); + $this->data = $this->setdata(); + } + + /** Find All Data */ + public function findAll($req, $res) { /** Filter Column Query Params */ $filter = $req->getQueryParams()['filter'] ?? null; /** Filter Data */ $data = $this->data; - + // Selected Column Fetch All Data if ($filter): $data = []; $filter = explode(",", $filter); - + foreach ($this->data as $item) - array_push($data, array_intersect_key((array) $item, array_flip($filter))); + array_push($data, array_intersect_key((array) $item, array_flip($filter))); endif; return response($req, $res, new Response(data: $data)); @@ -34,16 +48,16 @@ public function findAll($req, $res) { /** Find Data by Column */ public function findByColumn($req, $res, $args) { - + foreach ($args as $key => $value) - $data = array_filter($this->data, fn($item) => $item->$key == $args[$key]); + $data = array_filter($this->data, fn($item) => $item->$key == $args[$key]); checkNull($data, $req); return response($req, $res, args: new Response(data: [...$data])); } /** Delete Data by Id */ - public function delete($req, $res, $args) { + public function delete($req, $res, $args) { $data = $this->model->delete($args['id']); checkNull($data, $req); return response($req, $res, new Response(message: "This item has been successfully removed.", data: $data)); diff --git a/src/api/category/controller.php b/src/api/category/controller.php index d2c7cbc..9183900 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -2,15 +2,20 @@ declare(strict_types=1); namespace RestJS\Api\Category; -use RestJS\Trait\Controller as CoreController; +use RestJS\Abstract\Controller as AbstractController; use RestJS\Api\Category\Model; -class Controller { +class Controller extends AbstractController { function __construct(private Model $model) { - $this->data = $this->model->findAll(); + parent::__construct(); } - // Trait Controller - use CoreController; + protected function setModel() { + return $this->model; + } + + protected function setData() { + return $this->model->findAll(); + } } \ No newline at end of file diff --git a/src/trait/GetterAndSetter.php b/src/trait/GetterAndSetter.php deleted file mode 100644 index 736adae..0000000 --- a/src/trait/GetterAndSetter.php +++ /dev/null @@ -1,17 +0,0 @@ -$key; - } - - /** Set Value by Column */ - public function __set($key, $value) { - $this->$key = $value; - } -} \ No newline at end of file From 055d05b06154b5d7db6f77ced0e6ceb7e651c5a7 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 14:42:08 +0530 Subject: [PATCH 51/80] perf: merge authorization and controller abstract --- src/abstract/authorization.php | 12 +----------- src/abstract/controller.php | 2 +- src/api/category/controller.php | 2 +- src/api/user/controller.php | 9 ++++----- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/abstract/authorization.php b/src/abstract/authorization.php index 78e4dab..a93444d 100644 --- a/src/abstract/authorization.php +++ b/src/abstract/authorization.php @@ -9,17 +9,7 @@ use Slim\Exception\HttpUnauthorizedException; /** Abstract Authorization Functions */ -abstract class Authorization { - - /** Model Class */ - private $model; - - /** Abstract Function for Set Model Class */ - abstract protected function setModel(); - - public function __construct() { - $this->model = $this->setModel(); - } +abstract class Authorization extends Controller { /** Login Function */ public function login($req, $res) { diff --git a/src/abstract/controller.php b/src/abstract/controller.php index 8dcf3aa..7ed63ba 100644 --- a/src/abstract/controller.php +++ b/src/abstract/controller.php @@ -20,7 +20,7 @@ abstract protected function setModel(); /** Abstract Function for Set Table Data */ abstract protected function setData(); - function __construct() { + public function __construct() { $this->model = $this->setModel(); $this->data = $this->setdata(); } diff --git a/src/api/category/controller.php b/src/api/category/controller.php index 9183900..7044656 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -7,7 +7,7 @@ class Controller extends AbstractController { - function __construct(private Model $model) { + public function __construct(private Model $model) { parent::__construct(); } diff --git a/src/api/user/controller.php b/src/api/user/controller.php index 719b6c1..a669b62 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -2,14 +2,12 @@ declare(strict_types=1); namespace RestJS\Api\User; -use RestJS\Trait\Controller as CoreController; use RestJS\Abstract\Authorization as AuthController; use RestJS\Api\User\Model; class Controller extends AuthController { - function __construct(private Model $model) { - $this->data = $this->model->findAll(); + public function __construct(protected Model $model) { parent::__construct(); } @@ -17,6 +15,7 @@ protected function setModel() { return $this->model; } - // Trait Controller - use CoreController; + protected function setData() { + return $this->model->findAll(); + } } \ No newline at end of file From ba0d4f1d1f85f5ee3b2b18adc101e82451bd2875 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 17:43:08 +0530 Subject: [PATCH 52/80] perf: use casting for value conversion --- src/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.php b/src/app.php index bb2f1ec..e59ac7c 100644 --- a/src/app.php +++ b/src/app.php @@ -32,7 +32,7 @@ public static function create($dir) { $app->setBasePath("/api"); // set base path $app->addBodyParsingMiddleware(); // It is used to get json and form body data $app->add(new TrailingSlash(trailingSlash: true)); // It is used to stop shash error - $app->addErrorMiddleware(boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR']), boolval($_ENV['SHOW_ERROR'])); // It is used to get erors + $app->addErrorMiddleware((bool) $_ENV['SHOW_ERROR'], (bool) $_ENV['SHOW_ERROR'], (bool) $_ENV['SHOW_ERROR']); // It is used to get erors return $app; } From 7c6fa2d586bd875ce2d74d05dcf15a2d81203208 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 18:03:12 +0530 Subject: [PATCH 53/80] chore: change function visibility and variable name --- src/abstract/authorization.php | 4 ++-- src/abstract/controller.php | 8 ++++---- src/abstract/model.php | 16 ++++++++-------- src/api/category/model.php | 2 +- src/api/category/router.php | 2 +- src/api/user/model.php | 2 +- src/api/user/router.php | 2 +- src/middleware/authorization.php | 4 ++-- src/middleware/upload.php | 9 ++++----- 9 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/abstract/authorization.php b/src/abstract/authorization.php index a93444d..dd5c602 100644 --- a/src/abstract/authorization.php +++ b/src/abstract/authorization.php @@ -14,10 +14,10 @@ abstract class Authorization extends Controller { /** Login Function */ public function login($req, $res) { - /** Username Variable */ + /** Username */ $username = false; - /** Password Variable */ + /** Password */ $password = false; // Overide Username and Password Value diff --git a/src/abstract/controller.php b/src/abstract/controller.php index 7ed63ba..6b45871 100644 --- a/src/abstract/controller.php +++ b/src/abstract/controller.php @@ -8,16 +8,16 @@ /** Abstract Controller Functions */ abstract class Controller { - /** Entity or Table All Data */ + /** Entity All Data */ private $data; - /** Model Class */ + /** Model Class Object */ private $model; - /** Abstract Function for Set Model Class */ + /** Abstract Function for Set Model Class Object */ abstract protected function setModel(); - /** Abstract Function for Set Table Data */ + /** Abstract Function for Set Entity All Data */ abstract protected function setData(); public function __construct() { diff --git a/src/abstract/model.php b/src/abstract/model.php index 3a9932e..a84876e 100644 --- a/src/abstract/model.php +++ b/src/abstract/model.php @@ -7,18 +7,18 @@ /** Abstract Model Functions */ abstract class Model { - /** Create Repository from Table */ + /** Create Repository from Entity */ private $repository; - /** Entity or Table */ - private $table; + /** Entity Class String */ + private $entity; - /** Abstract Function for Set Table Class */ - abstract protected function setTable(); + /** Abstract Function for Set Entity Class String */ + abstract protected function setEntity(); public function __construct(private EntityManager $entityManager) { - $this->table = $this->setTable(); - $this->repository = $entityManager->getRepository($this->table); + $this->entity = $this->setEntity(); + $this->repository = $entityManager->getRepository($this->entity); } /** Find All Data */ @@ -60,7 +60,7 @@ public function update(array $args, string $id) { /** Insert Data */ public function insert(array $args) { - $data = new $this->table; + $data = new $this->entity; foreach ($args as $key => $value) $data->__set($key, $value); diff --git a/src/api/category/model.php b/src/api/category/model.php index 5b62d67..b2ae09d 100644 --- a/src/api/category/model.php +++ b/src/api/category/model.php @@ -7,7 +7,7 @@ class Model extends AbstractModel { - protected function setTable() { + protected function setEntity() { return Category::class; } } \ No newline at end of file diff --git a/src/api/category/router.php b/src/api/category/router.php index fd5c7e3..79eb160 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -6,7 +6,7 @@ use RestJS\Api\Category\Controller; class Router { - function __invoke(RouteCollectorProxy $router) { + public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); diff --git a/src/api/user/model.php b/src/api/user/model.php index 4297ef3..7a65605 100644 --- a/src/api/user/model.php +++ b/src/api/user/model.php @@ -7,7 +7,7 @@ class Model extends AbstractModel { - protected function setTable() { + protected function setEntity() { return User::class; } } \ No newline at end of file diff --git a/src/api/user/router.php b/src/api/user/router.php index e188248..5b5adfc 100644 --- a/src/api/user/router.php +++ b/src/api/user/router.php @@ -7,7 +7,7 @@ use RestJS\Middleware\Upload; class Router { - function __invoke(RouteCollectorProxy $router) { + public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); diff --git a/src/middleware/authorization.php b/src/middleware/authorization.php index c725619..6b470dc 100644 --- a/src/middleware/authorization.php +++ b/src/middleware/authorization.php @@ -14,9 +14,9 @@ /** Authorization Middleware */ class Authorization implements MiddlewareInterface { - function __construct(private User $user) {} + public function __construct(private User $user) {} - function process(Request $req, RequestHandler $handler): ResponseInterface { + public function process(Request $req, RequestHandler $handler): ResponseInterface { /** User Access Token */ $token = $_COOKIE['SSID'] ?? str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; diff --git a/src/middleware/upload.php b/src/middleware/upload.php index 114600f..87e022f 100644 --- a/src/middleware/upload.php +++ b/src/middleware/upload.php @@ -11,7 +11,7 @@ /** File Upload Middleware */ class Upload implements MiddlewareInterface { - function process(Request $req, RequestHandler $handler): ResponseInterface { + public function process(Request $req, RequestHandler $handler): ResponseInterface { /** User Upload File */ $uploadedFile = $req->getUploadedFiles(); @@ -28,9 +28,8 @@ function process(Request $req, RequestHandler $handler): ResponseInterface { return $handler->handle($req); } - /** File Upload to Server Function */ - function moveUploadedFile(string $directory, string $type, UploadedFileInterface $uploadedFile) { + private function moveUploadedFile(string $directory, string $type, UploadedFileInterface $uploadedFile) { /** File Extension */ $ext = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION); @@ -42,8 +41,8 @@ function moveUploadedFile(string $directory, string $type, UploadedFileInterface !is_dir($directory) && mkdir($directory); // File Upload to Server - $uploadedFile->moveTo($directory . $filename); + $uploadedFile->moveTo("{$directory}{$filename}"); - return $directory . $filename; + return "{$directory}{$filename}"; } } \ No newline at end of file From 1207ad52af6ecb8ab476da32b2023192306995d1 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 19:41:30 +0530 Subject: [PATCH 54/80] fix: duplicate variable name --- src/abstract/controller.php | 2 +- src/api/category/controller.php | 6 +++--- src/api/user/controller.php | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/abstract/controller.php b/src/abstract/controller.php index 6b45871..a199a40 100644 --- a/src/abstract/controller.php +++ b/src/abstract/controller.php @@ -12,7 +12,7 @@ abstract class Controller { private $data; /** Model Class Object */ - private $model; + protected $model; /** Abstract Function for Set Model Class Object */ abstract protected function setModel(); diff --git a/src/api/category/controller.php b/src/api/category/controller.php index 7044656..507ba3e 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -7,15 +7,15 @@ class Controller extends AbstractController { - public function __construct(private Model $model) { + public function __construct(private Model $_model) { parent::__construct(); } protected function setModel() { - return $this->model; + return $this->_model; } protected function setData() { - return $this->model->findAll(); + return $this->_model->findAll(); } } \ No newline at end of file diff --git a/src/api/user/controller.php b/src/api/user/controller.php index a669b62..e495faf 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -7,15 +7,15 @@ class Controller extends AuthController { - public function __construct(protected Model $model) { + public function __construct(protected Model $_model) { parent::__construct(); } protected function setModel() { - return $this->model; + return $this->_model; } protected function setData() { - return $this->model->findAll(); + return $this->_model->findAll(); } } \ No newline at end of file From 36d5868b30ce4fd8b356dc62d4a805d0f450c419 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 25 Jan 2025 20:07:28 +0530 Subject: [PATCH 55/80] refactor: identify entity functions --- src/api/category/Category.php | 4 +- src/api/user/User.php | 32 +--------------- src/class/AuthEntity.php | 37 +++++++++++++++++++ src/class/{getterandsetter.php => Entity.php} | 4 +- 4 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 src/class/AuthEntity.php rename src/class/{getterandsetter.php => Entity.php} (81%) diff --git a/src/api/category/Category.php b/src/api/category/Category.php index fd7b1b3..9fb7b39 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -3,11 +3,11 @@ namespace RestJS\Api\Category; use Doctrine\ORM\Mapping as ORM; -use RestJS\Class\GetterAndSetter; +use RestJS\Class\Entity; #[ORM\Entity] #[ORM\Table('category')] -class Category extends GetterAndSetter { +class Category extends Entity { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] diff --git a/src/api/user/User.php b/src/api/user/User.php index 93f38ea..fb5ca70 100644 --- a/src/api/user/User.php +++ b/src/api/user/User.php @@ -3,14 +3,12 @@ namespace RestJS\Api\User; use Doctrine\ORM\Mapping as ORM; -use Doctrine\ORM\Event as Event; -use Firebase\JWT\JWT; -use RestJS\Class\GetterAndSetter; +use RestJS\Class\AuthEntity; #[ORM\Entity] #[ORM\Table('user')] #[ORM\HasLifecycleCallbacks] -class User extends GetterAndSetter { +class User extends AuthEntity { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] @@ -36,30 +34,4 @@ class User extends GetterAndSetter { #[ORM\Column(name: "updated_at", insertable: false, updatable: false)] public string $updatedAt; - - #[ORM\PrePersist] - public function prePersist() { - $this->password = password_hash($this->password, PASSWORD_BCRYPT); - } - - #[ORM\PreUpdate] - public function preUpdate(Event\PreUpdateEventArgs $event) { - $passwordModify = $event->hasChangedField('password') ?? false; - - if ($passwordModify) - $this->password = password_hash($this->password, PASSWORD_BCRYPT); - } - - /** Verify Password Function */ - public function verifyPassword($password) { - return password_verify($password, $this->password); - } - - /** Generate Access Token Function */ - public function generateAccessToken() { - return JWT::encode([ - 'id' => $this->id, - 'username' => $this->username - ], $_ENV['ACCESS_TOKEN_SECRET'], 'HS256'); - } } \ No newline at end of file diff --git a/src/class/AuthEntity.php b/src/class/AuthEntity.php new file mode 100644 index 0000000..3964efd --- /dev/null +++ b/src/class/AuthEntity.php @@ -0,0 +1,37 @@ +password = password_hash($this->password, PASSWORD_BCRYPT); + } + + #[ORM\PreUpdate] + public function preUpdate(Event\PreUpdateEventArgs $event) { + $passwordModify = $event->hasChangedField('password') ?? false; + + if ($passwordModify) + $this->password = password_hash($this->password, PASSWORD_BCRYPT); + } + + /** Verify Password Function */ + public function verifyPassword($password) { + return password_verify($password, $this->password); + } + + /** Generate Access Token Function */ + public function generateAccessToken() { + return JWT::encode([ + 'id' => $this->id, + 'username' => $this->username + ], $_ENV['ACCESS_TOKEN_SECRET'], 'HS256'); + } +} \ No newline at end of file diff --git a/src/class/getterandsetter.php b/src/class/Entity.php similarity index 81% rename from src/class/getterandsetter.php rename to src/class/Entity.php index df426c9..447f409 100644 --- a/src/class/getterandsetter.php +++ b/src/class/Entity.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace RestJS\Class; -/** Getter and Setter Functions */ -class GetterAndSetter { +/** Entity Functions */ +class Entity { /** Get Value by Column */ public function __get($key) { From 2471226d26a0e37b9596d1356d346d51dbe9201c Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 27 Jan 2025 12:21:41 +0530 Subject: [PATCH 56/80] refactor: rename folder and files --- src/{app.php => App.php} | 0 .../AbstractAuthController.php} | 8 ++++---- .../AbstractController.php} | 20 +++++++------------ src/{database.php => Database.php} | 0 .../AbstractAuthEntity.php} | 6 +++--- .../Entity.php => Entity/AbstractEntity.php} | 6 +++--- .../response.php => Message/Response.php} | 2 +- .../upload.php => Middleware/Upload.php} | 0 .../model.php => Model/AbstractModel.php} | 4 ++-- src/api/category/Category.php | 4 ++-- src/api/category/controller.php | 14 +++---------- src/api/category/model.php | 2 +- src/api/user/User.php | 4 ++-- src/api/user/controller.php | 16 ++++----------- src/api/user/model.php | 2 +- 15 files changed, 33 insertions(+), 55 deletions(-) rename src/{app.php => App.php} (100%) rename src/{abstract/authorization.php => Controller/AbstractAuthController.php} (91%) rename src/{abstract/controller.php => Controller/AbstractController.php} (82%) rename src/{database.php => Database.php} (100%) rename src/{class/AuthEntity.php => Entity/AbstractAuthEntity.php} (88%) rename src/{class/Entity.php => Entity/AbstractEntity.php} (75%) rename src/{class/response.php => Message/Response.php} (94%) rename src/{middleware/upload.php => Middleware/Upload.php} (100%) rename src/{abstract/model.php => Model/AbstractModel.php} (96%) diff --git a/src/app.php b/src/App.php similarity index 100% rename from src/app.php rename to src/App.php diff --git a/src/abstract/authorization.php b/src/Controller/AbstractAuthController.php similarity index 91% rename from src/abstract/authorization.php rename to src/Controller/AbstractAuthController.php index dd5c602..6f4f72c 100644 --- a/src/abstract/authorization.php +++ b/src/Controller/AbstractAuthController.php @@ -1,15 +1,15 @@ model = $this->setModel(); - $this->data = $this->setdata(); + public function __construct($model, $data) { + $this->data = $data; + $this->model = $model; } /** Find All Data */ diff --git a/src/database.php b/src/Database.php similarity index 100% rename from src/database.php rename to src/Database.php diff --git a/src/class/AuthEntity.php b/src/Entity/AbstractAuthEntity.php similarity index 88% rename from src/class/AuthEntity.php rename to src/Entity/AbstractAuthEntity.php index 3964efd..183033d 100644 --- a/src/class/AuthEntity.php +++ b/src/Entity/AbstractAuthEntity.php @@ -1,13 +1,13 @@ _model; - } - - protected function setData() { - return $this->_model->findAll(); + public function __construct(private Model $model) { + parent::__construct($model, $model->findAll()); } } \ No newline at end of file diff --git a/src/api/category/model.php b/src/api/category/model.php index b2ae09d..e5dc6e2 100644 --- a/src/api/category/model.php +++ b/src/api/category/model.php @@ -2,7 +2,7 @@ declare(strict_types=1); namespace RestJS\Api\Category; -use RestJS\Abstract\Model as AbstractModel; +use RestJS\Model\AbstractModel; use RestJS\Api\Category\Category; class Model extends AbstractModel { diff --git a/src/api/user/User.php b/src/api/user/User.php index fb5ca70..1581d03 100644 --- a/src/api/user/User.php +++ b/src/api/user/User.php @@ -3,12 +3,12 @@ namespace RestJS\Api\User; use Doctrine\ORM\Mapping as ORM; -use RestJS\Class\AuthEntity; +use RestJS\Entity\AbstractAuthEntity; #[ORM\Entity] #[ORM\Table('user')] #[ORM\HasLifecycleCallbacks] -class User extends AuthEntity { +class User extends AbstractAuthEntity { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] diff --git a/src/api/user/controller.php b/src/api/user/controller.php index e495faf..12fe71f 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -2,20 +2,12 @@ declare(strict_types=1); namespace RestJS\Api\User; -use RestJS\Abstract\Authorization as AuthController; +use RestJS\Controller\AbstractAuthController; use RestJS\Api\User\Model; -class Controller extends AuthController { +class Controller extends AbstractAuthController { - public function __construct(protected Model $_model) { - parent::__construct(); - } - - protected function setModel() { - return $this->_model; - } - - protected function setData() { - return $this->_model->findAll(); + public function __construct(private Model $model) { + parent::__construct($model, $model->findAll()); } } \ No newline at end of file diff --git a/src/api/user/model.php b/src/api/user/model.php index 7a65605..b3985a5 100644 --- a/src/api/user/model.php +++ b/src/api/user/model.php @@ -2,7 +2,7 @@ declare(strict_types=1); namespace RestJS\Api\User; -use RestJS\Abstract\Model as AbstractModel; +use RestJS\Model\AbstractModel; use RestJS\Api\User\User; class Model extends AbstractModel { From 2ba7e40e72627fb0f6abffb085bf04b739e8267a Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 27 Jan 2025 12:31:14 +0530 Subject: [PATCH 57/80] perf: authorization middleware convert abstract --- src/Middleware/AbstractAuthMiddleware.php | 52 +++++++++++++++++++++++ src/middleware/authorization.php | 42 ++---------------- 2 files changed, 56 insertions(+), 38 deletions(-) create mode 100644 src/Middleware/AbstractAuthMiddleware.php diff --git a/src/Middleware/AbstractAuthMiddleware.php b/src/Middleware/AbstractAuthMiddleware.php new file mode 100644 index 0000000..4d55be0 --- /dev/null +++ b/src/Middleware/AbstractAuthMiddleware.php @@ -0,0 +1,52 @@ +user = $user; + } + + public function process(Request $req, RequestHandler $handler): ResponseInterface { + + /** User Access Token */ + $token = $_COOKIE['SSID'] ?? str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; + + if (!$token) + throw new HttpUnauthorizedException($req, 'Unauthorized request'); + + try { + /** Decode Json Web Token */ + $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); + } + catch (\Exception $e) { + $decodedToken = null; + } + + if (!$decodedToken) + throw new HttpUnauthorizedException($req, "Invalid access token"); + + /** Check User Entity */ + $user = $this->user->findById($decodedToken['id']); + + if (!$user) + throw new HttpUnauthorizedException($req, "Invalid access token"); + + $req->user = $user; + + return $handler->handle($req); + } +} \ No newline at end of file diff --git a/src/middleware/authorization.php b/src/middleware/authorization.php index 6b470dc..452afbb 100644 --- a/src/middleware/authorization.php +++ b/src/middleware/authorization.php @@ -2,47 +2,13 @@ declare(strict_types=1); namespace RestJS\Middleware; -use Firebase\JWT\JWT; -use Firebase\JWT\Key; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Http\Server\RequestHandlerInterface as RequestHandler; -use Psr\Http\Server\MiddlewareInterface; use RestJS\Api\User\Model as User; -use Slim\Exception\HttpUnauthorizedException; +use RestJS\Middleware\AbstractAuthMiddleware; /** Authorization Middleware */ -class Authorization implements MiddlewareInterface { +class Authorization extends AbstractAuthMiddleware { - public function __construct(private User $user) {} - - public function process(Request $req, RequestHandler $handler): ResponseInterface { - - /** User Access Token */ - $token = $_COOKIE['SSID'] ?? str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; - - if (!$token) - throw new HttpUnauthorizedException($req, 'Unauthorized request'); - - try { - /** Decode Json Web Token */ - $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); - } - catch (\Exception $e) { - $decodedToken = null; - } - - if (!$decodedToken) - throw new HttpUnauthorizedException($req, "Invalid access token"); - - /** Check User Entity */ - $user = $this->user->findById($decodedToken['id']); - - if (!$user) - throw new HttpUnauthorizedException($req, "Invalid access token"); - - $req->user = $user; - - return $handler->handle($req); + public function __construct(private User $user) { + parent::__construct($user); } } \ No newline at end of file From 534c91b1bb4b752bffb2a4fecf6ba6ba2e3bf5eb Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 27 Jan 2025 12:48:53 +0530 Subject: [PATCH 58/80] fix: duplicate variable name --- src/Controller/AbstractAuthController.php | 2 +- src/Controller/AbstractController.php | 20 ++++++++++---------- src/Middleware/AbstractAuthMiddleware.php | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 6f4f72c..19bc641 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -28,7 +28,7 @@ public function login($req, $res) { throw new HttpBadRequestException($req, "Username or password is required."); /** Check User Entity */ - $user = errorHandler($this->model->findBy(['username' => $username])); + $user = errorHandler($this->_model->findBy(['username' => $username])); if (!$user) throw new HttpUnauthorizedException($req, 'Invalid user credentials'); diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index b1887d7..61321ce 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -9,14 +9,14 @@ class AbstractController { /** Entity All Data */ - private $data; + private $_data; /** Model Class Object */ - private $model; + protected $_model; public function __construct($model, $data) { - $this->data = $data; - $this->model = $model; + $this->_data = $data; + $this->_model = $model; } /** Find All Data */ @@ -26,14 +26,14 @@ public function findAll($req, $res) { $filter = $req->getQueryParams()['filter'] ?? null; /** Filter Data */ - $data = $this->data; + $data = $this->_data; // Selected Column Fetch All Data if ($filter): $data = []; $filter = explode(",", $filter); - foreach ($this->data as $item) + foreach ($this->_data as $item) array_push($data, array_intersect_key((array) $item, array_flip($filter))); endif; @@ -44,7 +44,7 @@ public function findAll($req, $res) { public function findByColumn($req, $res, $args) { foreach ($args as $key => $value) - $data = array_filter($this->data, fn($item) => $item->$key == $args[$key]); + $data = array_filter($this->_data, fn($item) => $item->$key == $args[$key]); checkNull($data, $req); return response($req, $res, args: new Response(data: [...$data])); @@ -52,20 +52,20 @@ public function findByColumn($req, $res, $args) { /** Delete Data by Id */ public function delete($req, $res, $args) { - $data = $this->model->delete($args['id']); + $data = $this->_model->delete($args['id']); checkNull($data, $req); return response($req, $res, new Response(message: "This item has been successfully removed.", data: $data)); } /** Insert Data */ public function insert($req, $res, $args) { - $data = $this->model->insert($req->getParsedBody()); + $data = $this->_model->insert($req->getParsedBody()); return response($req, $res, new Response(message: "This item has been successfully added.", data: $data)); } /** Update by Id */ public function update($req, $res, $args) { - $data = $this->model->update($req->getParsedBody(), $args["id"]); + $data = $this->_model->update($req->getParsedBody(), $args["id"]); checkNull($data, $req); return response($req, $res, new Response(message: "This item has been successfully updated.", data: $data)); } diff --git a/src/Middleware/AbstractAuthMiddleware.php b/src/Middleware/AbstractAuthMiddleware.php index 4d55be0..9c4c724 100644 --- a/src/Middleware/AbstractAuthMiddleware.php +++ b/src/Middleware/AbstractAuthMiddleware.php @@ -14,10 +14,10 @@ abstract class AbstractAuthMiddleware implements MiddlewareInterface { /** User Class Object */ - private $user; + private $_user; public function __construct($user) { - $this->user = $user; + $this->_user = $user; } public function process(Request $req, RequestHandler $handler): ResponseInterface { @@ -40,7 +40,7 @@ public function process(Request $req, RequestHandler $handler): ResponseInterfac throw new HttpUnauthorizedException($req, "Invalid access token"); /** Check User Entity */ - $user = $this->user->findById($decodedToken['id']); + $user = $this->_user->findById($decodedToken['id']); if (!$user) throw new HttpUnauthorizedException($req, "Invalid access token"); From 36bad0b82cd02e81e43ad914429117f3e4cb40d1 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 27 Jan 2025 18:19:57 +0530 Subject: [PATCH 59/80] fix: relationship functions --- src/relationship.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/relationship.php b/src/relationship.php index aee67ef..b9be458 100644 --- a/src/relationship.php +++ b/src/relationship.php @@ -7,7 +7,11 @@ function manyToOne($many, $one, $property) { $result = []; foreach ($many as $manyItem): - $filter = array_filter($one, fn($item) => $manyItem[$property] == $item['id']); + + // Object Convert to Array + $manyItem = (array) $manyItem; + + $filter = array_filter($one, fn($item) => $manyItem[$property] == $item->id); $manyItem[$property] = [...$filter][0]; array_push($result, $manyItem); endforeach; @@ -21,13 +25,16 @@ function oneToMany($one, $many, $property) { foreach ($one as $oneItem): + // Object Convert to Array + $oneItem = (array) $oneItem; + if ($oneItem[$property] == null) $oneItem[$property] = []; else { $ids = json_decode($oneItem[$property]); $oneItem[$property] = []; foreach ($ids as $id): - $array = array_filter($many, fn($item) => $id == $item['id']); + $array = array_filter($many, fn($item) => $id == $item->id); array_push($oneItem[$property], ...$array); endforeach; } @@ -43,10 +50,10 @@ function oneWayFilter($current, $filter, $addProperty, $findProperty) { $result = []; foreach ($current as $currentItem): - $currentItem[$addProperty] = []; + $currentItem->$addProperty = []; - $array = array_filter($filter, fn($item) => $currentItem['id'] == $item[$findProperty]); - array_push($currentItem[$addProperty], ...$array); + $array = array_filter($filter, fn($item) => $currentItem->id == $item->$findProperty); + array_push($currentItem->$addProperty, ...$array); array_push($result, $currentItem); endforeach; From f40103aeb44009413be469fd757720684a1ce19f Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 29 Jan 2025 22:13:17 +0530 Subject: [PATCH 60/80] chore: authorization with refresh token --- .env-sample | 8 ++++++-- src/Controller/AbstractAuthController.php | 9 +++++++-- src/Entity/AbstractAuthEntity.php | 16 ++++++++++++++-- src/Middleware/AbstractAuthMiddleware.php | 20 ++++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/.env-sample b/.env-sample index cc8f661..bdb31ff 100644 --- a/.env-sample +++ b/.env-sample @@ -9,5 +9,9 @@ CORS_ORIGIN = * SHOW_ERROR = 1 or 0 # Access Token Variables -ACCESS_TOKEN_SECRET = -ACCESS_TOKEN_EXPIRY = \ No newline at end of file +ACCESS_TOKEN_SECRET = +ACCESS_TOKEN_EXPIRY = In Minutes + +# Refresh Token Variables +REFRESH_TOKEN_SECRET = +REFRESH_TOKEN_EXPIRY = In Days \ No newline at end of file diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 19bc641..6054b25 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -8,7 +8,7 @@ use Slim\Exception\HttpBadRequestException; use Slim\Exception\HttpUnauthorizedException; -/** Abstract Authorization Controller Functions */ +/** Abstract Authentication Controller Functions */ class AbstractAuthController extends AbstractController { /** Login Function */ @@ -44,8 +44,12 @@ public function login($req, $res) { /** Generated Access Token */ $accessToken = $user->generateAccessToken(); + /** Generated Refresh Token */ + $refreshToken = $user->generateRefreshToken(); + // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 84600 * intval($_ENV['ACCESS_TOKEN_EXPIRY']), path: '/', secure: true, httponly: true); + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); + setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/api/', secure: true, httponly: true); return response($req, $res, new Response(message: "User logged in successfully.", data: ['accessToken' => $accessToken])); } @@ -55,6 +59,7 @@ public function logout($req, $res) { // Remove Authorization Cookies setcookie('SSID', '', time() - 100, path: '/', secure: true, httponly: true); + setcookie('RTID', '', time() - 100, path: '/api/', secure: true, httponly: true); return response($req, $res, new Response(message: "User logged out successfully.")); } diff --git a/src/Entity/AbstractAuthEntity.php b/src/Entity/AbstractAuthEntity.php index 183033d..66bb2c0 100644 --- a/src/Entity/AbstractAuthEntity.php +++ b/src/Entity/AbstractAuthEntity.php @@ -6,7 +6,7 @@ use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Event as Event; -/** Abstract Authorization Entity Functions */ +/** Abstract Authentication Entity Functions */ class AbstractAuthEntity extends AbstractEntity { #[ORM\PrePersist] @@ -31,7 +31,19 @@ public function verifyPassword($password) { public function generateAccessToken() { return JWT::encode([ 'id' => $this->id, - 'username' => $this->username + 'iat' => time(), + 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], ], $_ENV['ACCESS_TOKEN_SECRET'], 'HS256'); } + + /** Generate Refresh Token Function */ + public function generateRefreshToken() { + return JWT::encode([ + 'id' => $this->id, + 'username' => $this->username, + 'name' => $this->name, + 'iat' => time(), + 'exp' => time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], + ], $_ENV['REFRESH_TOKEN_SECRET'], 'HS256'); + } } \ No newline at end of file diff --git a/src/Middleware/AbstractAuthMiddleware.php b/src/Middleware/AbstractAuthMiddleware.php index 9c4c724..15c107e 100644 --- a/src/Middleware/AbstractAuthMiddleware.php +++ b/src/Middleware/AbstractAuthMiddleware.php @@ -25,6 +25,26 @@ public function process(Request $req, RequestHandler $handler): ResponseInterfac /** User Access Token */ $token = $_COOKIE['SSID'] ?? str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; + /** Server Access Token */ + $refreshToken = $_COOKIE['RTID'] ?? null; + + // Genrate Access Token to Refresh Token + if ($refreshToken && !$token): + + /** Decode Json Web Token */ + $decodedToken = (array) JWT::decode($refreshToken, new Key($_ENV['REFRESH_TOKEN_SECRET'], 'HS256')); + + /** Check User Entity */ + $user = $this->_user->findById($decodedToken['id']); + + /** Generated Access Token */ + $accessToken = $user->generateAccessToken(); + + // Add Authorization Cookies + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); + $token = $accessToken; + endif; + if (!$token) throw new HttpUnauthorizedException($req, 'Unauthorized request'); From 62e28e4c6cabefd6bbd84b94ca637d960349bb1b Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Thu, 30 Jan 2025 20:31:36 +0530 Subject: [PATCH 61/80] chore: regenerate access token function --- index.php | 1 + src/Controller/AbstractAuthController.php | 30 +++++++++++++++++++++++ src/Middleware/AbstractAuthMiddleware.php | 22 +---------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/index.php b/index.php index 0f8a856..762ff61 100644 --- a/index.php +++ b/index.php @@ -13,6 +13,7 @@ // Routers $app->post('/login/', [UserController::class, 'login']); $app->get('/logout/', [UserController::class, 'logout'])->add(Authorization::class); +$app->post('/refreshtoken/', [UserController::class, 'regenerateAccessToken']); $app->group('/category', CategoryRouter::class)->add(Authorization::class); $app->group('/user', UserRouter::class)->add(Authorization::class); diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 6054b25..ba31945 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -2,6 +2,8 @@ declare(strict_types=1); namespace RestJS\Controller; +use Firebase\JWT\JWT; +use Firebase\JWT\Key; use RestJS\Message\Response; use function RestJS\errorHandler; use function RestJS\response; @@ -63,4 +65,32 @@ public function logout($req, $res) { return response($req, $res, new Response(message: "User logged out successfully.")); } + + /** Regenrate Access Token to Refresh Token */ + public function regenerateAccessToken($req, $res) { + + /** User Refresh Token */ + $refreshToken = $req->getParsedBody()['refresh_token'] ?? null; + + try { + /** Decode Json Web Token */ + $decodedToken = (array) JWT::decode($refreshToken, new Key($_ENV['REFRESH_TOKEN_SECRET'], 'HS256')); + } catch (\Exception $e) { + $decodedToken = null; + } + + if (!$decodedToken) + throw new HttpUnauthorizedException($req, "Invalid access token"); + + /** Check User Entity */ + $user = $this->_model->findById($decodedToken['id']); + + /** Generated Access Token */ + $accessToken = $user->generateAccessToken(); + + // Add Authorization Cookies + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); + + return response($req, $res, new Response(message: "User regenrate access token successfully.", data: ['accessToken' => $accessToken])); + } } \ No newline at end of file diff --git a/src/Middleware/AbstractAuthMiddleware.php b/src/Middleware/AbstractAuthMiddleware.php index 15c107e..c5f1943 100644 --- a/src/Middleware/AbstractAuthMiddleware.php +++ b/src/Middleware/AbstractAuthMiddleware.php @@ -23,27 +23,7 @@ public function __construct($user) { public function process(Request $req, RequestHandler $handler): ResponseInterface { /** User Access Token */ - $token = $_COOKIE['SSID'] ?? str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; - - /** Server Access Token */ - $refreshToken = $_COOKIE['RTID'] ?? null; - - // Genrate Access Token to Refresh Token - if ($refreshToken && !$token): - - /** Decode Json Web Token */ - $decodedToken = (array) JWT::decode($refreshToken, new Key($_ENV['REFRESH_TOKEN_SECRET'], 'HS256')); - - /** Check User Entity */ - $user = $this->_user->findById($decodedToken['id']); - - /** Generated Access Token */ - $accessToken = $user->generateAccessToken(); - - // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); - $token = $accessToken; - endif; + $token = str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; if (!$token) throw new HttpUnauthorizedException($req, 'Unauthorized request'); From c22a6f8da2dc321b4ff3cf700df9b5f3937e2279 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 31 Jan 2025 09:22:25 +0530 Subject: [PATCH 62/80] chore: share login user detail and refresh token --- src/Controller/AbstractAuthController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index ba31945..4d39a50 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -51,9 +51,9 @@ public function login($req, $res) { // Add Authorization Cookies setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); - setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/api/', secure: true, httponly: true); + setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); - return response($req, $res, new Response(message: "User logged in successfully.", data: ['accessToken' => $accessToken])); + return response($req, $res, new Response(message: "User logged in successfully.", data: ['user'=> $user, 'accessToken' => $accessToken, 'refreshToken' => $refreshToken])); } /** Logout Function */ @@ -61,7 +61,7 @@ public function logout($req, $res) { // Remove Authorization Cookies setcookie('SSID', '', time() - 100, path: '/', secure: true, httponly: true); - setcookie('RTID', '', time() - 100, path: '/api/', secure: true, httponly: true); + setcookie('RTID', '', time() - 100, path: '/', secure: true, httponly: true); return response($req, $res, new Response(message: "User logged out successfully.")); } From 1c474c560288cbf30a21f5e57cf6f1640db3ffc7 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Fri, 31 Jan 2025 19:33:54 +0530 Subject: [PATCH 63/80] chore: remove relationship on controller --- src/Controller/AbstractController.php | 29 +++------------------------ src/api/category/controller.php | 2 +- src/api/user/controller.php | 2 +- src/relationship.php | 15 -------------- 4 files changed, 5 insertions(+), 43 deletions(-) diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index 61321ce..823441c 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -8,44 +8,21 @@ /** Abstract Controller Functions */ class AbstractController { - /** Entity All Data */ - private $_data; - /** Model Class Object */ protected $_model; - public function __construct($model, $data) { - $this->_data = $data; + public function __construct($model) { $this->_model = $model; } /** Find All Data */ public function findAll($req, $res) { - - /** Filter Column Query Params */ - $filter = $req->getQueryParams()['filter'] ?? null; - - /** Filter Data */ - $data = $this->_data; - - // Selected Column Fetch All Data - if ($filter): - $data = []; - $filter = explode(",", $filter); - - foreach ($this->_data as $item) - array_push($data, array_intersect_key((array) $item, array_flip($filter))); - endif; - - return response($req, $res, new Response(data: $data)); + return response($req, $res, new Response(data: $this->_model->findAll())); } /** Find Data by Column */ public function findByColumn($req, $res, $args) { - - foreach ($args as $key => $value) - $data = array_filter($this->_data, fn($item) => $item->$key == $args[$key]); - + $data = $this->_model->findBy($args); checkNull($data, $req); return response($req, $res, args: new Response(data: [...$data])); } diff --git a/src/api/category/controller.php b/src/api/category/controller.php index bf37c10..868a6bc 100644 --- a/src/api/category/controller.php +++ b/src/api/category/controller.php @@ -8,6 +8,6 @@ class Controller extends AbstractController { public function __construct(private Model $model) { - parent::__construct($model, $model->findAll()); + parent::__construct($model); } } \ No newline at end of file diff --git a/src/api/user/controller.php b/src/api/user/controller.php index 12fe71f..87638c3 100644 --- a/src/api/user/controller.php +++ b/src/api/user/controller.php @@ -8,6 +8,6 @@ class Controller extends AbstractAuthController { public function __construct(private Model $model) { - parent::__construct($model, $model->findAll()); + parent::__construct($model); } } \ No newline at end of file diff --git a/src/relationship.php b/src/relationship.php index b9be458..45674d8 100644 --- a/src/relationship.php +++ b/src/relationship.php @@ -42,20 +42,5 @@ function oneToMany($one, $many, $property) { array_push($result, $oneItem); endforeach; - return $result; -} - -/** One Way Filter Relationship Function */ -function oneWayFilter($current, $filter, $addProperty, $findProperty) { - $result = []; - - foreach ($current as $currentItem): - $currentItem->$addProperty = []; - - $array = array_filter($filter, fn($item) => $currentItem->id == $item->$findProperty); - array_push($currentItem->$addProperty, ...$array); - array_push($result, $currentItem); - endforeach; - return $result; } \ No newline at end of file From bc3ff3a08ef3b87c6f8d7e6fd5533445a224a0db Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 1 Feb 2025 14:31:31 +0530 Subject: [PATCH 64/80] chore: remove all relationship functions --- composer.json | 3 +- src/Controller/AbstractAuthController.php | 2 +- src/relationship.php | 46 ----------------------- 3 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 src/relationship.php diff --git a/composer.json b/composer.json index aa42c2e..4b9e9d7 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,7 @@ "license": "MIT", "autoload": { "files": [ - "src/function.php", - "src/relationship.php" + "src/function.php" ], "psr-4": { "RestJS\\": "src/" diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 4d39a50..5bcd61b 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -70,7 +70,7 @@ public function logout($req, $res) { public function regenerateAccessToken($req, $res) { /** User Refresh Token */ - $refreshToken = $req->getParsedBody()['refresh_token'] ?? null; + $refreshToken = $req->getParsedBody()['refreshToken'] ?? null; try { /** Decode Json Web Token */ diff --git a/src/relationship.php b/src/relationship.php deleted file mode 100644 index 45674d8..0000000 --- a/src/relationship.php +++ /dev/null @@ -1,46 +0,0 @@ - $manyItem[$property] == $item->id); - $manyItem[$property] = [...$filter][0]; - array_push($result, $manyItem); - endforeach; - - return $result; -} - -/** One To Many Relationship Function */ -function oneToMany($one, $many, $property) { - $result = []; - - foreach ($one as $oneItem): - - // Object Convert to Array - $oneItem = (array) $oneItem; - - if ($oneItem[$property] == null) $oneItem[$property] = []; - else { - $ids = json_decode($oneItem[$property]); - $oneItem[$property] = []; - - foreach ($ids as $id): - $array = array_filter($many, fn($item) => $id == $item->id); - array_push($oneItem[$property], ...$array); - endforeach; - } - - array_push($result, $oneItem); - endforeach; - - return $result; -} \ No newline at end of file From 08296941e986b34d2756d2f1e019821b9fc6c133 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 1 Feb 2025 20:26:08 +0530 Subject: [PATCH 65/80] chore: update controller and model for relationship --- src/Controller/AbstractController.php | 6 ++--- src/Model/AbstractModel.php | 33 +++++++++++++++------------ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index 823441c..b8371b6 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -29,20 +29,20 @@ public function findByColumn($req, $res, $args) { /** Delete Data by Id */ public function delete($req, $res, $args) { - $data = $this->_model->delete($args['id']); + $data = $this->_model->delete($args); checkNull($data, $req); return response($req, $res, new Response(message: "This item has been successfully removed.", data: $data)); } /** Insert Data */ public function insert($req, $res, $args) { - $data = $this->_model->insert($req->getParsedBody()); + $data = $this->_model->insert([...$req->getParsedBody(), ...$args ]); return response($req, $res, new Response(message: "This item has been successfully added.", data: $data)); } /** Update by Id */ public function update($req, $res, $args) { - $data = $this->_model->update($req->getParsedBody(), $args["id"]); + $data = $this->_model->update($req->getParsedBody(), $args); checkNull($data, $req); return response($req, $res, new Response(message: "This item has been successfully updated.", data: $data)); } diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index bc1511c..eb57854 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -27,8 +27,8 @@ public function findAll(): array { } /** Find Data by Conditional */ - public function findBy($array): array { - return $this->repository->findBy($array); + public function findBy(array $args): array { + return $this->repository->findBy($args); } /** Find Data by Id */ @@ -37,25 +37,30 @@ public function findById($id) { } /** Delete Data by Id */ - public function delete(string $id) { - $data = $this->repository->find($id); - $this->entityManager->remove($data); - $this->entityManager->flush(); + public function delete(array $args) { + $data = $this->repository->findBy($args)[0] ?? null; - return $data; + if ($data): + $this->entityManager->remove($data); + $this->entityManager->flush(); + + return $data; + endif; } /** Update Data by Id */ - public function update(array $args, string $id) { - $data = $this->repository->find($id); + public function update(array $data, array $args) { + $item = $this->repository->findBy($args)[0] ?? null; - foreach ($args as $key => $value) - $data->__set($key, $value); + if ($item): + foreach ($data as $key => $value) + $item->__set($key, $value); - $this->entityManager->merge($data); - $this->entityManager->flush(); + $this->entityManager->merge($item); + $this->entityManager->flush(); - return $data; + return $data; + endif; } /** Insert Data */ From 13c07dd5bc991303a8f29eeaf352b189df477b41 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 1 Feb 2025 20:27:19 +0530 Subject: [PATCH 66/80] chore: update api route for relationship --- index.php | 14 ++++++++------ src/api/category/Category.php | 10 +++++----- src/api/category/router.php | 4 ---- src/api/user/User.php | 7 ++----- src/api/user/router.php | 13 ++++++++----- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/index.php b/index.php index 762ff61..2a0eda9 100644 --- a/index.php +++ b/index.php @@ -10,12 +10,14 @@ /** Create Application */ $app = App::create(__DIR__); -// Routers -$app->post('/login/', [UserController::class, 'login']); -$app->get('/logout/', [UserController::class, 'logout'])->add(Authorization::class); -$app->post('/refreshtoken/', [UserController::class, 'regenerateAccessToken']); -$app->group('/category', CategoryRouter::class)->add(Authorization::class); -$app->group('/user', UserRouter::class)->add(Authorization::class); +// Authentication Routes +$app->post('/auth/login/', [UserController::class, 'login']); +$app->get('/auth/logout/', [UserController::class, 'logout'])->add(Authorization::class); +$app->post('/auth/refreshtoken/', [UserController::class, 'regenerateAccessToken']); + +// Application Routes +$app->group('/categories', CategoryRouter::class)->add(Authorization::class); +$app->group('/users', UserRouter::class)->add(Authorization::class); // Application Execute or Run $app->run(); \ No newline at end of file diff --git a/src/api/category/Category.php b/src/api/category/Category.php index b4e3680..f27b079 100644 --- a/src/api/category/Category.php +++ b/src/api/category/Category.php @@ -6,24 +6,24 @@ use RestJS\Entity\AbstractEntity; #[ORM\Entity] -#[ORM\Table('category')] +#[ORM\Table('categories')] class Category extends AbstractEntity { #[ORM\Id] #[ORM\Column, ORM\GeneratedValue] public int $id; - #[ORM\Column(unique: true)] + #[ORM\Column] public string $title; #[ORM\Column(unique: true)] public string $slug; - #[ORM\Column] + #[ORM\Column(nullable: true)] public string $description; - #[ORM\Column(name: "author_id")] - public int $authorId; + #[ORM\Column(name: "user_id")] + public string $userId; #[ORM\Column(name: "created_at", insertable: false, updatable: false)] public string $createdAt; diff --git a/src/api/category/router.php b/src/api/category/router.php index 79eb160..23ec3bd 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -8,10 +8,6 @@ class Router { public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); - $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); - $router->put('/{id:[0-9]+}/', [Controller::class, "update"]); - $router->post('/', [Controller::class, "insert"]); - $router->delete('/{id:[0-9]+}/', [Controller::class, "delete"]); } } \ No newline at end of file diff --git a/src/api/user/User.php b/src/api/user/User.php index 1581d03..10e2d1b 100644 --- a/src/api/user/User.php +++ b/src/api/user/User.php @@ -6,7 +6,7 @@ use RestJS\Entity\AbstractAuthEntity; #[ORM\Entity] -#[ORM\Table('user')] +#[ORM\Table('users')] #[ORM\HasLifecycleCallbacks] class User extends AbstractAuthEntity { @@ -14,7 +14,7 @@ class User extends AbstractAuthEntity { #[ORM\Column, ORM\GeneratedValue] public int $id; - #[ORM\Column(unique: true)] + #[ORM\Column] public string $name; #[ORM\Column(unique: true)] @@ -26,9 +26,6 @@ class User extends AbstractAuthEntity { #[ORM\Column(nullable: true)] public string $image; - #[ORM\Column(nullable: true)] - public string $logo; - #[ORM\Column(name: "created_at", insertable: false, updatable: false)] public string $createdAt; diff --git a/src/api/user/router.php b/src/api/user/router.php index 5b5adfc..9785f8d 100644 --- a/src/api/user/router.php +++ b/src/api/user/router.php @@ -4,15 +4,18 @@ use Slim\Routing\RouteCollectorProxy; use RestJS\Api\User\Controller; -use RestJS\Middleware\Upload; +use RestJS\Api\Category\Controller as CategoryController; class Router { public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); - $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); - $router->put('/{id:[0-9]+}/', [Controller::class, "update"])->add(Upload::class); - $router->post('/', [Controller::class, "insert"])->add(Upload::class); - $router->delete('/{id:[0-9]+}/', [Controller::class, "delete"]); + + // Category Route + $router->get('/{userId:[0-9]+}/categories/', [CategoryController::class, "findByColumn"]); + $router->get('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "findByColumn"]); + $router->post('/{userId:[0-9]+}/categories/', [CategoryController::class, "insert"]); + $router->put('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "update"]); + $router->delete('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "delete"]); } } \ No newline at end of file From 073c0d7d34e0506d280f08eafcd73cd7c03e75e0 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sun, 2 Feb 2025 08:58:37 +0530 Subject: [PATCH 67/80] chore: variables name change --- src/Model/AbstractModel.php | 30 +++++++++++++++--------------- src/api/category/router.php | 1 + src/api/user/router.php | 1 + 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index eb57854..0443fdf 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -38,41 +38,41 @@ public function findById($id) { /** Delete Data by Id */ public function delete(array $args) { - $data = $this->repository->findBy($args)[0] ?? null; + $row = $this->repository->findBy($args)[0] ?? null; - if ($data): - $this->entityManager->remove($data); + if ($row): + $this->entityManager->remove($row); $this->entityManager->flush(); - return $data; + return $row; endif; } /** Update Data by Id */ public function update(array $data, array $args) { - $item = $this->repository->findBy($args)[0] ?? null; + $row = $this->repository->findBy($args)[0] ?? null; - if ($item): + if ($row): foreach ($data as $key => $value) - $item->__set($key, $value); + $row->__set($key, $value); - $this->entityManager->merge($item); + $this->entityManager->merge($row); $this->entityManager->flush(); - return $data; + return $row; endif; } /** Insert Data */ - public function insert(array $args) { - $data = new $this->entity; + public function insert(array $data) { + $row = new $this->entity; - foreach ($args as $key => $value) - $data->__set($key, $value); + foreach ($data as $key => $value) + $row->__set($key, $value); - $this->entityManager->persist($data); + $this->entityManager->persist($row); $this->entityManager->flush(); - return $data; + return $row; } } \ No newline at end of file diff --git a/src/api/category/router.php b/src/api/category/router.php index 23ec3bd..6ac74f5 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -8,6 +8,7 @@ class Router { public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); } } \ No newline at end of file diff --git a/src/api/user/router.php b/src/api/user/router.php index 9785f8d..f60c43e 100644 --- a/src/api/user/router.php +++ b/src/api/user/router.php @@ -9,6 +9,7 @@ class Router { public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); // Category Route From ba273ce0e297d8ce7f599af34fccddce6e5396e0 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 3 Feb 2025 17:32:04 +0530 Subject: [PATCH 68/80] fix: array first item --- src/Controller/AbstractAuthController.php | 4 +--- src/Middleware/AbstractAuthMiddleware.php | 2 +- src/Model/AbstractModel.php | 13 +++++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 5bcd61b..9a7b3b9 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -30,13 +30,11 @@ public function login($req, $res) { throw new HttpBadRequestException($req, "Username or password is required."); /** Check User Entity */ - $user = errorHandler($this->_model->findBy(['username' => $username])); + $user = errorHandler($this->_model->filter(['username' => $username])); if (!$user) throw new HttpUnauthorizedException($req, 'Invalid user credentials'); - $user = $user[0]; - /** Verify User Password */ $isValidPassword = $user->verifyPassword($password); diff --git a/src/Middleware/AbstractAuthMiddleware.php b/src/Middleware/AbstractAuthMiddleware.php index c5f1943..b2575fb 100644 --- a/src/Middleware/AbstractAuthMiddleware.php +++ b/src/Middleware/AbstractAuthMiddleware.php @@ -23,7 +23,7 @@ public function __construct($user) { public function process(Request $req, RequestHandler $handler): ResponseInterface { /** User Access Token */ - $token = str_replace('Bearer ', '', $req->getHeader('Authorization'))[0] ?? $req->getQueryParams()['accessToken'] ?? null; + $token = str_replace('Bearer ', '', $req->getHeaderLine('Authorization')) ?? $req->getQueryParams()['accessToken'] ?? null; if (!$token) throw new HttpUnauthorizedException($req, 'Unauthorized request'); diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index 0443fdf..dfeb359 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -31,14 +31,19 @@ public function findBy(array $args): array { return $this->repository->findBy($args); } + /** Filter Data by Conditional */ + public function filter(array $args): object { + return $this->repository->findOneBy($args); + } + /** Find Data by Id */ - public function findById($id) { + public function findById($id): object { return $this->repository->find($id); } /** Delete Data by Id */ public function delete(array $args) { - $row = $this->repository->findBy($args)[0] ?? null; + $row = $this->repository->findOneBy($args) ?? null; if ($row): $this->entityManager->remove($row); @@ -50,7 +55,7 @@ public function delete(array $args) { /** Update Data by Id */ public function update(array $data, array $args) { - $row = $this->repository->findBy($args)[0] ?? null; + $row = $this->repository->findOneBy($args) ?? null; if ($row): foreach ($data as $key => $value) @@ -64,7 +69,7 @@ public function update(array $data, array $args) { } /** Insert Data */ - public function insert(array $data) { + public function insert(array $data): object { $row = new $this->entity; foreach ($data as $key => $value) From ce9c67311c0c1f8d2ff4764cbaca765ad57d8613 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 4 Feb 2025 17:47:08 +0530 Subject: [PATCH 69/80] fix: null user input post and put method --- src/Controller/AbstractController.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index b8371b6..b7a35c9 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -4,6 +4,7 @@ use RestJS\Message\Response; use function RestJS\response, RestJS\checkNull; +use Slim\Exception\HttpBadRequestException; /** Abstract Controller Functions */ class AbstractController { @@ -23,7 +24,6 @@ public function findAll($req, $res) { /** Find Data by Column */ public function findByColumn($req, $res, $args) { $data = $this->_model->findBy($args); - checkNull($data, $req); return response($req, $res, args: new Response(data: [...$data])); } @@ -36,12 +36,18 @@ public function delete($req, $res, $args) { /** Insert Data */ public function insert($req, $res, $args) { - $data = $this->_model->insert([...$req->getParsedBody(), ...$args ]); + if (!$req->getParsedBody()) + throw new HttpBadRequestException($req, "Please enter valid form data."); + + $data = $this->_model->insert([...$req->getParsedBody(), ...$args]); return response($req, $res, new Response(message: "This item has been successfully added.", data: $data)); } /** Update by Id */ public function update($req, $res, $args) { + if (!$req->getParsedBody()) + throw new HttpBadRequestException($req, "Please enter valid form data."); + $data = $this->_model->update($req->getParsedBody(), $args); checkNull($data, $req); return response($req, $res, new Response(message: "This item has been successfully updated.", data: $data)); From 5332aed64e0a308ff7a6951ef332676b10e9f124 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 4 Feb 2025 18:34:11 +0530 Subject: [PATCH 70/80] perf: remove all check functions --- src/Controller/AbstractAuthController.php | 8 ++------ src/Controller/AbstractController.php | 20 ++++++++++++++++---- src/Middleware/AbstractAuthMiddleware.php | 10 +++------- src/Model/AbstractModel.php | 4 ++-- src/function.php | 15 --------------- 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 9a7b3b9..af777a0 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -5,7 +5,6 @@ use Firebase\JWT\JWT; use Firebase\JWT\Key; use RestJS\Message\Response; -use function RestJS\errorHandler; use function RestJS\response; use Slim\Exception\HttpBadRequestException; use Slim\Exception\HttpUnauthorizedException; @@ -30,7 +29,7 @@ public function login($req, $res) { throw new HttpBadRequestException($req, "Username or password is required."); /** Check User Entity */ - $user = errorHandler($this->_model->filter(['username' => $username])); + $user = $this->_model->filter(['username' => $username]); if (!$user) throw new HttpUnauthorizedException($req, 'Invalid user credentials'); @@ -74,11 +73,8 @@ public function regenerateAccessToken($req, $res) { /** Decode Json Web Token */ $decodedToken = (array) JWT::decode($refreshToken, new Key($_ENV['REFRESH_TOKEN_SECRET'], 'HS256')); } catch (\Exception $e) { - $decodedToken = null; + throw new HttpUnauthorizedException($req, "Invalid access token"); } - - if (!$decodedToken) - throw new HttpUnauthorizedException($req, "Invalid access token"); /** Check User Entity */ $user = $this->_model->findById($decodedToken['id']); diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index b7a35c9..6fc3812 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -3,7 +3,7 @@ namespace RestJS\Controller; use RestJS\Message\Response; -use function RestJS\response, RestJS\checkNull; +use function RestJS\response; use Slim\Exception\HttpBadRequestException; /** Abstract Controller Functions */ @@ -30,13 +30,19 @@ public function findByColumn($req, $res, $args) { /** Delete Data by Id */ public function delete($req, $res, $args) { $data = $this->_model->delete($args); - checkNull($data, $req); + + // Check data is deleted + if (!$data) + throw new HttpBadRequestException($req, "Something went wrong..."); + return response($req, $res, new Response(message: "This item has been successfully removed.", data: $data)); } /** Insert Data */ public function insert($req, $res, $args) { - if (!$req->getParsedBody()) + + // Check user input valid form data + if(!$req->getParsedBody()) throw new HttpBadRequestException($req, "Please enter valid form data."); $data = $this->_model->insert([...$req->getParsedBody(), ...$args]); @@ -45,11 +51,17 @@ public function insert($req, $res, $args) { /** Update by Id */ public function update($req, $res, $args) { + + // check user input valid form data if (!$req->getParsedBody()) throw new HttpBadRequestException($req, "Please enter valid form data."); $data = $this->_model->update($req->getParsedBody(), $args); - checkNull($data, $req); + + // Check data is updated + if (!$data) + throw new HttpBadRequestException($req, "Something went wrong..."); + return response($req, $res, new Response(message: "This item has been successfully updated.", data: $data)); } } \ No newline at end of file diff --git a/src/Middleware/AbstractAuthMiddleware.php b/src/Middleware/AbstractAuthMiddleware.php index b2575fb..5fb73c0 100644 --- a/src/Middleware/AbstractAuthMiddleware.php +++ b/src/Middleware/AbstractAuthMiddleware.php @@ -31,14 +31,10 @@ public function process(Request $req, RequestHandler $handler): ResponseInterfac try { /** Decode Json Web Token */ $decodedToken = (array) JWT::decode($token, new Key($_ENV['ACCESS_TOKEN_SECRET'], 'HS256')); - } - catch (\Exception $e) { - $decodedToken = null; - } - - if (!$decodedToken) + } catch (\Exception $e) { throw new HttpUnauthorizedException($req, "Invalid access token"); - + } + /** Check User Entity */ $user = $this->_user->findById($decodedToken['id']); diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index dfeb359..ac7bbc4 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -32,12 +32,12 @@ public function findBy(array $args): array { } /** Filter Data by Conditional */ - public function filter(array $args): object { + public function filter(array $args): object|null { return $this->repository->findOneBy($args); } /** Find Data by Id */ - public function findById($id): object { + public function findById($id): object|null { return $this->repository->find($id); } diff --git a/src/function.php b/src/function.php index 0d5d4be..85fd6db 100644 --- a/src/function.php +++ b/src/function.php @@ -2,7 +2,6 @@ declare(strict_types=1); namespace RestJS; -use Slim\Exception\HttpBadRequestException; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -10,18 +9,4 @@ function response(Request $req, Response $res, mixed $args) { $res->getBody()->write(json_encode($args)); return $res; -} - -/** Check Null Value Function */ -function checkNull($value, $req) { - !boolval($value) && throw new HttpBadRequestException($req, "Something went wrong..."); -} - -/** Callback Error Handler Function */ -function errorHandler($callback) { - try { - return $callback; - } catch (\Exception $e) { - return null; - } } \ No newline at end of file From 3d2faef551c834708853c8bd6b2e86d344b283c6 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Tue, 4 Feb 2025 23:16:11 +0530 Subject: [PATCH 71/80] chore: filter, pagination and limit in filter function --- composer.json | 3 +- src/Controller/AbstractController.php | 24 +++++++++----- src/api/category/router.php | 4 +-- src/api/user/router.php | 6 ++-- src/filter.php | 47 +++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 src/filter.php diff --git a/composer.json b/composer.json index 4b9e9d7..a6e679f 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "license": "MIT", "autoload": { "files": [ - "src/function.php" + "src/function.php", + "src/filter.php" ], "psr-4": { "RestJS\\": "src/" diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index 6fc3812..64079b2 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -3,7 +3,7 @@ namespace RestJS\Controller; use RestJS\Message\Response; -use function RestJS\response; +use function RestJS\response, RestJS\filter; use Slim\Exception\HttpBadRequestException; /** Abstract Controller Functions */ @@ -18,20 +18,26 @@ public function __construct($model) { /** Find All Data */ public function findAll($req, $res) { - return response($req, $res, new Response(data: $this->_model->findAll())); + $data = filter($this->_model->findAll(), $req); + return response($req, $res, new Response(data: $data)); } - /** Find Data by Column */ + /** Find Data by Conditional */ public function findByColumn($req, $res, $args) { - $data = $this->_model->findBy($args); - return response($req, $res, args: new Response(data: [...$data])); + $data = filter($this->_model->findBy($args), $req); + return response($req, $res, args: new Response(data: $data)); + } + + /** Filter Data by Conditional */ + public function filter($req, $res, $args) { + return response($req, $res, args: new Response(data: $this->_model->filter($args))); } /** Delete Data by Id */ public function delete($req, $res, $args) { $data = $this->_model->delete($args); - // Check data is deleted + // Check Data is Deleted if (!$data) throw new HttpBadRequestException($req, "Something went wrong..."); @@ -41,7 +47,7 @@ public function delete($req, $res, $args) { /** Insert Data */ public function insert($req, $res, $args) { - // Check user input valid form data + // Check User Input Valid Form Data if(!$req->getParsedBody()) throw new HttpBadRequestException($req, "Please enter valid form data."); @@ -52,13 +58,13 @@ public function insert($req, $res, $args) { /** Update by Id */ public function update($req, $res, $args) { - // check user input valid form data + // Check User Input Valid Form Data if (!$req->getParsedBody()) throw new HttpBadRequestException($req, "Please enter valid form data."); $data = $this->_model->update($req->getParsedBody(), $args); - // Check data is updated + // Check Data is Updated if (!$data) throw new HttpBadRequestException($req, "Something went wrong..."); diff --git a/src/api/category/router.php b/src/api/category/router.php index 6ac74f5..c5d72c2 100644 --- a/src/api/category/router.php +++ b/src/api/category/router.php @@ -8,7 +8,7 @@ class Router { public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); - $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); - $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "filter"]); + $router->get('/{slug:[a-z0-9-]+}/', [Controller::class, "filter"]); } } \ No newline at end of file diff --git a/src/api/user/router.php b/src/api/user/router.php index f60c43e..42c65e7 100644 --- a/src/api/user/router.php +++ b/src/api/user/router.php @@ -9,12 +9,12 @@ class Router { public function __invoke(RouteCollectorProxy $router) { $router->get('/', [Controller::class, "findAll"]); - $router->get('/{id:[0-9]+}/', [Controller::class, "findByColumn"]); - $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "findByColumn"]); + $router->get('/{id:[0-9]+}/', [Controller::class, "filter"]); + $router->get('/{username:[a-z0-9-]+}/', [Controller::class, "filter"]); // Category Route $router->get('/{userId:[0-9]+}/categories/', [CategoryController::class, "findByColumn"]); - $router->get('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "findByColumn"]); + $router->get('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "filter"]); $router->post('/{userId:[0-9]+}/categories/', [CategoryController::class, "insert"]); $router->put('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "update"]); $router->delete('/{userId:[0-9]+}/categories/{id:[0-9]+}/', [CategoryController::class, "delete"]); diff --git a/src/filter.php b/src/filter.php new file mode 100644 index 0000000..ca4b69b --- /dev/null +++ b/src/filter.php @@ -0,0 +1,47 @@ +getQueryParams() ?? []); + + /** After Filter Data */ + $filterData = []; + + /** Filter Column Query Params */ + $filter ??= null; + + /** Pagination Page Number Query Params */ + $page ??= 1; + + /** Per Page Limit Query Params */ + $limit ??= 10; + + // Selected Column Fetch All Data + if ($filter): + $filter = explode(",", $filter); + + foreach ($data as $item) + array_push($filterData, array_intersect_key((array) $item, array_flip($filter))); + endif; + + // According Pagination Fetch All Data + if ($page): + $filter && $data = $filterData; + $filterData = array_slice($data, $page == 1 ? 0 : $limit * ($page - 1), (int) $limit); + endif; + + return [ + "page" => $page, + "limit" => $limit, + "totalPages" => ceil(count($data) / $limit), + "totalItems" => count($data), + "currentPageItems" => count($filterData), + 'data' => $filterData, + ]; +} \ No newline at end of file From 7fc861eebb294c6590e783bee3ebfbc9e3f0727e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 5 Feb 2025 11:36:29 +0530 Subject: [PATCH 72/80] chore: search in filter function --- src/filter.php | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/filter.php b/src/filter.php index ca4b69b..34cd7b1 100644 --- a/src/filter.php +++ b/src/filter.php @@ -22,17 +22,44 @@ function filter($data, Request $req): array { /** Per Page Limit Query Params */ $limit ??= 10; + /** Search Query Params */ + $search ??= null; + + // Search Column Fetch All Data + if ($search): + foreach ($data as $item): + $isSearch = false; + + foreach (array_values((array) $item) as $value): + $searchWith = str_contains(strtolower(trim(strip_tags((string) $value))), strtolower(trim($search))); + + if ($searchWith) + $isSearch = true; + endforeach; + + if ($isSearch) + array_push($filterData, $item); + + endforeach; + endif; + // Selected Column Fetch All Data if ($filter): $filter = explode(",", $filter); + if ($search) + $data = $filterData; + + $filterData = []; + foreach ($data as $item) array_push($filterData, array_intersect_key((array) $item, array_flip($filter))); endif; // According Pagination Fetch All Data if ($page): - $filter && $data = $filterData; + if ($filter || $search) + $data = $filterData; $filterData = array_slice($data, $page == 1 ? 0 : $limit * ($page - 1), (int) $limit); endif; From fae5a6179689f4eb2b7af28ff84fe9a7e0d95851 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 5 Feb 2025 12:40:13 +0530 Subject: [PATCH 73/80] chore: reverse fetch data --- src/filter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/filter.php b/src/filter.php index 34cd7b1..efb36e5 100644 --- a/src/filter.php +++ b/src/filter.php @@ -7,6 +7,9 @@ /** Filter Function for Response Data Manipulation */ function filter($data, Request $req): array { + /** Query Fetch Data */ + $data = array_reverse($data); + // Get Query Params Values extract($req->getQueryParams() ?? []); From bf66ba316e88c8f91dc0bebff0e064a6ade4d47d Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 5 Feb 2025 15:43:42 +0530 Subject: [PATCH 74/80] chore: sort by order in filter function --- src/filter.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/filter.php b/src/filter.php index efb36e5..8cf5b4c 100644 --- a/src/filter.php +++ b/src/filter.php @@ -28,7 +28,13 @@ function filter($data, Request $req): array { /** Search Query Params */ $search ??= null; - // Search Column Fetch All Data + /** Sort Query Params */ + $sort ??= null; + + /** Sort Order Query Params */ + $order ??= 'asc'; // asc or desc + + // Search All Column Fetch All Data if ($search): foreach ($data as $item): $isSearch = false; @@ -46,11 +52,19 @@ function filter($data, Request $req): array { endforeach; endif; + // Sorted Column Fetch All Data + if ($sort): + $filterData = empty($filterData) ? $data : $filterData; + + usort($filterData, fn($a, $b) => + $order == 'asc' ? $a->$sort > $b->$sort : $a->$sort < $b->$sort); + endif; + // Selected Column Fetch All Data if ($filter): $filter = explode(",", $filter); - if ($search) + if ($search || $sort) $data = $filterData; $filterData = []; @@ -61,8 +75,9 @@ function filter($data, Request $req): array { // According Pagination Fetch All Data if ($page): - if ($filter || $search) + if ($filter || $search || $sort) $data = $filterData; + $filterData = array_slice($data, $page == 1 ? 0 : $limit * ($page - 1), (int) $limit); endif; From 5b109b94b830d47930890d329851f0edbb1ae791 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Wed, 5 Feb 2025 17:29:47 +0530 Subject: [PATCH 75/80] chore: share login user detail with access token --- src/Controller/AbstractAuthController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index af777a0..c8c12d2 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -85,6 +85,6 @@ public function regenerateAccessToken($req, $res) { // Add Authorization Cookies setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); - return response($req, $res, new Response(message: "User regenrate access token successfully.", data: ['accessToken' => $accessToken])); + return response($req, $res, new Response(message: "User regenrate access token successfully.", data: ['user' => $user, 'accessToken' => $accessToken])); } } \ No newline at end of file From 0dbc93902acd6184ba99aa58a68dcc563380b11b Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Thu, 6 Feb 2025 11:37:30 +0530 Subject: [PATCH 76/80] fix: search after filter entity --- src/filter.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/filter.php b/src/filter.php index 8cf5b4c..1865f29 100644 --- a/src/filter.php +++ b/src/filter.php @@ -34,8 +34,21 @@ function filter($data, Request $req): array { /** Sort Order Query Params */ $order ??= 'asc'; // asc or desc + // Selected Column Fetch All Data + if ($filter): + $filter = explode(",", $filter); + + foreach ($data as $item) + array_push($filterData, array_intersect_key((array) $item, array_flip($filter))); + endif; + // Search All Column Fetch All Data if ($search): + if ($filter) + $data = $filterData; + + $filterData = []; + foreach ($data as $item): $isSearch = false; @@ -57,20 +70,7 @@ function filter($data, Request $req): array { $filterData = empty($filterData) ? $data : $filterData; usort($filterData, fn($a, $b) => - $order == 'asc' ? $a->$sort > $b->$sort : $a->$sort < $b->$sort); - endif; - - // Selected Column Fetch All Data - if ($filter): - $filter = explode(",", $filter); - - if ($search || $sort) - $data = $filterData; - - $filterData = []; - - foreach ($data as $item) - array_push($filterData, array_intersect_key((array) $item, array_flip($filter))); + $order == 'asc' ? ((object) $a)->$sort > ((object) $b)->$sort : ((object) $a)->$sort < ((object) $b)->$sort); endif; // According Pagination Fetch All Data From 357c568c08ef276eb424b3262e50c810ea4ffba6 Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sun, 23 Feb 2025 20:15:31 +0530 Subject: [PATCH 77/80] chore: headers allow credentials for cookies --- src/App.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/App.php b/src/App.php index e59ac7c..39fa2f2 100644 --- a/src/App.php +++ b/src/App.php @@ -18,9 +18,10 @@ public static function create($dir) { // Header All Allows header("Access-Control-Allow-Origin: {$_ENV['CORS_ORIGIN']}"); + header("Access-Control-Allow-Credentials: true"); header("Content-Type: application/json; charset=UTF-8"); header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE"); - header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); + header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Access-Control-Allow-Credentials"); // Database Connection Database::connection(); From c6d1a916125a15746e50fef43c7d9b285132236b Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sun, 23 Feb 2025 23:05:11 +0530 Subject: [PATCH 78/80] perf: share access token and refresh token by cookies --- index.php | 2 +- src/Controller/AbstractAuthController.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/index.php b/index.php index 2a0eda9..484bbcf 100644 --- a/index.php +++ b/index.php @@ -13,7 +13,7 @@ // Authentication Routes $app->post('/auth/login/', [UserController::class, 'login']); $app->get('/auth/logout/', [UserController::class, 'logout'])->add(Authorization::class); -$app->post('/auth/refreshtoken/', [UserController::class, 'regenerateAccessToken']); +$app->get('/auth/refreshtoken/', [UserController::class, 'regenerateAccessToken']); // Application Routes $app->group('/categories', CategoryRouter::class)->add(Authorization::class); diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index c8c12d2..651ebc4 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -47,18 +47,18 @@ public function login($req, $res) { $refreshToken = $user->generateRefreshToken(); // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); - setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true); + setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/', secure: true); - return response($req, $res, new Response(message: "User logged in successfully.", data: ['user'=> $user, 'accessToken' => $accessToken, 'refreshToken' => $refreshToken])); + return response($req, $res, new Response(message: "User logged in successfully.")); } /** Logout Function */ public function logout($req, $res) { // Remove Authorization Cookies - setcookie('SSID', '', time() - 100, path: '/', secure: true, httponly: true); - setcookie('RTID', '', time() - 100, path: '/', secure: true, httponly: true); + setcookie('SSID', '', time() - 100, path: '/', secure: true); + setcookie('RTID', '', time() - 100, path: '/', secure: true); return response($req, $res, new Response(message: "User logged out successfully.")); } @@ -67,7 +67,7 @@ public function logout($req, $res) { public function regenerateAccessToken($req, $res) { /** User Refresh Token */ - $refreshToken = $req->getParsedBody()['refreshToken'] ?? null; + $refreshToken = $_COOKIE['RTID']; try { /** Decode Json Web Token */ @@ -83,8 +83,8 @@ public function regenerateAccessToken($req, $res) { $accessToken = $user->generateAccessToken(); // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true, httponly: true); + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true); - return response($req, $res, new Response(message: "User regenrate access token successfully.", data: ['user' => $user, 'accessToken' => $accessToken])); + return response($req, $res, new Response(message: "User regenrate access token successfully.")); } } \ No newline at end of file From 8b197880b7fec2b5912422d4d1a40b8c6c3077fa Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Sat, 8 Mar 2025 11:54:56 +0530 Subject: [PATCH 79/80] chore: share access and refresh token by response --- index.php | 4 ++-- src/Controller/AbstractAuthController.php | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/index.php b/index.php index 484bbcf..d7ae3d9 100644 --- a/index.php +++ b/index.php @@ -12,8 +12,8 @@ // Authentication Routes $app->post('/auth/login/', [UserController::class, 'login']); -$app->get('/auth/logout/', [UserController::class, 'logout'])->add(Authorization::class); -$app->get('/auth/refreshtoken/', [UserController::class, 'regenerateAccessToken']); +$app->post('/auth/logout/', [UserController::class, 'logout'])->add(Authorization::class); +$app->post('/auth/refreshtoken/', [UserController::class, 'regenerateAccessToken']); // Application Routes $app->group('/categories', CategoryRouter::class)->add(Authorization::class); diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 651ebc4..36531d7 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -47,18 +47,18 @@ public function login($req, $res) { $refreshToken = $user->generateRefreshToken(); // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true); - setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/', secure: true); + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); + setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); - return response($req, $res, new Response(message: "User logged in successfully.")); + return response($req, $res, new Response(data: ['userId' => $user->id, 'SSID' => ['token' => $accessToken, 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY']], 'RTID' => ['token' => $refreshToken, 'exp' => time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY']]])); } /** Logout Function */ public function logout($req, $res) { // Remove Authorization Cookies - setcookie('SSID', '', time() - 100, path: '/', secure: true); - setcookie('RTID', '', time() - 100, path: '/', secure: true); + setcookie('SSID', '', time() - 100, path: '/api', secure: true, httponly: true); + setcookie('RTID', '', time() - 100, path: '/api', secure: true, httponly: true); return response($req, $res, new Response(message: "User logged out successfully.")); } @@ -83,8 +83,8 @@ public function regenerateAccessToken($req, $res) { $accessToken = $user->generateAccessToken(); // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/', secure: true); + setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); - return response($req, $res, new Response(message: "User regenrate access token successfully.")); + return response($req, $res, new Response(data: ['SSID' => ['token' => $accessToken, 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY']]])); } } \ No newline at end of file From ed00a4f6c5ffcae8596bb206a6ab7c5e387e831e Mon Sep 17 00:00:00 2001 From: Pramod Singh Gusain Date: Mon, 10 Mar 2025 09:00:14 +0530 Subject: [PATCH 80/80] chore: remove refresh and access token cookies --- src/Controller/AbstractAuthController.php | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Controller/AbstractAuthController.php b/src/Controller/AbstractAuthController.php index 36531d7..23b8b2b 100644 --- a/src/Controller/AbstractAuthController.php +++ b/src/Controller/AbstractAuthController.php @@ -47,18 +47,22 @@ public function login($req, $res) { $refreshToken = $user->generateRefreshToken(); // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); - setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); - - return response($req, $res, new Response(data: ['userId' => $user->id, 'SSID' => ['token' => $accessToken, 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY']], 'RTID' => ['token' => $refreshToken, 'exp' => time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY']]])); + // setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); + // setcookie('RTID', $refreshToken, time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); + + return response($req, $res, new Response(data: [ + 'userId' => $user->id, + 'SSID' => ['token' => $accessToken, 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY']], + 'RTID' => ['token' => $refreshToken, 'exp' => time() + 86400 * (int) $_ENV['REFRESH_TOKEN_EXPIRY']] + ])); } /** Logout Function */ public function logout($req, $res) { // Remove Authorization Cookies - setcookie('SSID', '', time() - 100, path: '/api', secure: true, httponly: true); - setcookie('RTID', '', time() - 100, path: '/api', secure: true, httponly: true); + // setcookie('SSID', '', time() - 100, path: '/api', secure: true, httponly: true); + // setcookie('RTID', '', time() - 100, path: '/api', secure: true, httponly: true); return response($req, $res, new Response(message: "User logged out successfully.")); } @@ -67,7 +71,7 @@ public function logout($req, $res) { public function regenerateAccessToken($req, $res) { /** User Refresh Token */ - $refreshToken = $_COOKIE['RTID']; + $refreshToken = $_COOKIE['RTID'] ?? $req->getParsedBody()['refreshToken'] ?? null; try { /** Decode Json Web Token */ @@ -83,8 +87,10 @@ public function regenerateAccessToken($req, $res) { $accessToken = $user->generateAccessToken(); // Add Authorization Cookies - setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); + // setcookie('SSID', $accessToken, time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY'], path: '/api', secure: true, httponly: true); - return response($req, $res, new Response(data: ['SSID' => ['token' => $accessToken, 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY']]])); + return response($req, $res, new Response(data: [ + 'SSID' => ['token' => $accessToken, 'exp' => time() + 60 * (int) $_ENV['ACCESS_TOKEN_EXPIRY']] + ])); } } \ No newline at end of file