From a7ceb105fb1c3b24ac3e77a987989296646fcfe6 Mon Sep 17 00:00:00 2001 From: David Cabral Date: Mon, 7 Oct 2024 21:01:27 -0300 Subject: [PATCH] Major: Refactor and first complete flow --- Dockerfile => .devops/Dockerfile | 2 +- .env.example | 9 + .gitignore | 2 +- README.md | 16 +- composer.json | 7 +- composer.lock | 1073 ++++++++++++++++- ...20241002030500_profile_table_migration.php | 41 + .../20241002030501_user_table_migration.php | 46 + docker-compose.yml | 42 + lang/en/validation.php | 9 + phinx.php | 39 + src/Adapters/Database/MysqlAdapter.php | 61 + src/Adapters/Token/JwtAdapter.php | 32 + src/Adapters/Validation/IlluminateAdapter.php | 39 + src/Base.php | 24 - src/Main.php | 29 +- .../Auth/Controller/AuthController.php | 74 -- .../Auth/Controllers/AuthController.php | 65 +- .../Auth/Entities/SignupRequestEntity.php | 15 + .../Auth/Entities/SignupResponseEntity.php | 12 + .../Auth/Middlewares/AuthMiddleware.php | 4 +- src/Modules/Auth/Model/AuthModel.php | 27 - .../Auth/Repositories/AuthRepository.php | 59 + src/Modules/Auth/Service/AuthService.php | 15 - src/Modules/Auth/Services/AuthService.php | 32 + .../User/Controllers/UserController.php | 64 - .../Util/Services/ValidationService.php | 26 + src/Routes.php | 53 +- 28 files changed, 1640 insertions(+), 277 deletions(-) rename Dockerfile => .devops/Dockerfile (95%) mode change 100755 => 100644 create mode 100644 config/db/migrations/20241002030500_profile_table_migration.php create mode 100644 config/db/migrations/20241002030501_user_table_migration.php create mode 100644 docker-compose.yml create mode 100644 lang/en/validation.php create mode 100644 phinx.php create mode 100644 src/Adapters/Database/MysqlAdapter.php create mode 100644 src/Adapters/Token/JwtAdapter.php create mode 100644 src/Adapters/Validation/IlluminateAdapter.php delete mode 100644 src/Base.php delete mode 100644 src/Modules/Auth/Controller/AuthController.php create mode 100644 src/Modules/Auth/Entities/SignupRequestEntity.php create mode 100644 src/Modules/Auth/Entities/SignupResponseEntity.php delete mode 100644 src/Modules/Auth/Model/AuthModel.php create mode 100644 src/Modules/Auth/Repositories/AuthRepository.php delete mode 100644 src/Modules/Auth/Service/AuthService.php create mode 100644 src/Modules/Auth/Services/AuthService.php delete mode 100644 src/Modules/User/Controllers/UserController.php create mode 100644 src/Modules/Util/Services/ValidationService.php diff --git a/Dockerfile b/.devops/Dockerfile old mode 100755 new mode 100644 similarity index 95% rename from Dockerfile rename to .devops/Dockerfile index 45dc9b0..14503dd --- a/Dockerfile +++ b/.devops/Dockerfile @@ -22,4 +22,4 @@ COPY . /api RUN composer install -# ENTRYPOINT [ "sh", "-c", "php public/index.php" ] +ENTRYPOINT [ "sh", "-c", "php public/index.php" ] diff --git a/.env.example b/.env.example index 6c5fe45..56b1638 100755 --- a/.env.example +++ b/.env.example @@ -1,4 +1,13 @@ # FIDELIFY API +DB_TYPE='' +DB_HOST='' +DB_PORT='' +DB_NAME='' +DB_USER='' +DB_PASS='' + +JWT_SECRET='' + SERVER_HOST='' SERVER_PORT='' diff --git a/.gitignore b/.gitignore index 95e7e21..cf1df7b 100755 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ vendor # project .env -.devops/data +.data diff --git a/README.md b/README.md index 507fe72..2eba511 100755 --- a/README.md +++ b/README.md @@ -4,5 +4,17 @@ > Docker required: [how to install](https://docs.docker.com/engine/install/ubuntu/). -1. `docker build -t fidelify/phpswoole .` -2. `docker run -it -v $(pwd):/api -p 8003:8003 --rm --name fidelify-api fidelify/phpswoole php public/index.php` +#### compose way + +1. `docker compose up -d` +2. `docker exec -it fidelify-api bash` +3. `vendor/bin/phinx migrate` + +#### standalone way + +1. `docker network create -d bridge local` +2. `docker run --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=fidelify --network local --name fidelify-db mysql:8.0.13` +3. `docker build -t fidelify/phpswoole .` +4. `docker run --rm -it -v c:/dev/fidelify-api:/api -p 8003:8003 --env-file .env --name fidelify-api fidelify/phpswoole` +5. `docker exec -it fidelify-api bash` +6. `vendor/bin/phinx migrate` diff --git a/composer.json b/composer.json index b2823bd..7089ab2 100755 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "fidelify/api", + "license": "MIT", "autoload": { "psr-4": { "Fidelify\\Api\\": "src/" @@ -14,8 +15,10 @@ "league/container": "^4.2", "laminas/laminas-diactoros": "^3.4", "laminas/laminas-httphandlerrunner": "^2.10", - "ilexn/swoole-convert-psr7": "^0.6.1", + "ilexn/swoole-convert-psr7": "^0.6", "nyholm/psr7": "^1.8", - "illuminate/validation": "^11.25" + "illuminate/validation": "^11.25", + "firebase/php-jwt": "^6.10", + "robmorgan/phinx": "^0.14" } } diff --git a/composer.lock b/composer.lock index df54168..379aefc 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d8941c5550d4c27a1a14410f2d35a91c", + "content-hash": "c57ce8c41688409a0e0b6970d97bec9d", "packages": [ { "name": "brick/math", @@ -66,6 +66,239 @@ ], "time": "2023-11-29T23:19:16+00:00" }, + { + "name": "cakephp/core", + "version": "4.5.7", + "source": { + "type": "git", + "url": "https://github.com/cakephp/core.git", + "reference": "c2f4dff110d41e475d1041f2abe236f1c62d0cd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/core/zipball/c2f4dff110d41e475d1041f2abe236f1c62d0cd0", + "reference": "c2f4dff110d41e475d1041f2abe236f1c62d0cd0", + "shasum": "" + }, + "require": { + "cakephp/utility": "^4.0", + "php": ">=7.4.0" + }, + "provide": { + "psr/container-implementation": "^1.0 || ^2.0" + }, + "suggest": { + "cakephp/cache": "To use Configure::store() and restore().", + "cakephp/event": "To use PluginApplicationInterface or plugin applications.", + "league/container": "To use Container and ServiceProvider classes" + }, + "type": "library", + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Cake\\Core\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/core/graphs/contributors" + } + ], + "description": "CakePHP Framework Core classes", + "homepage": "https://cakephp.org", + "keywords": [ + "cakephp", + "core", + "framework" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/core" + }, + "time": "2023-10-21T13:30:46+00:00" + }, + { + "name": "cakephp/database", + "version": "4.5.7", + "source": { + "type": "git", + "url": "https://github.com/cakephp/database.git", + "reference": "317739cc32060ef19b6c19c87ac6b64848d78e27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/database/zipball/317739cc32060ef19b6c19c87ac6b64848d78e27", + "reference": "317739cc32060ef19b6c19c87ac6b64848d78e27", + "shasum": "" + }, + "require": { + "cakephp/core": "^4.0", + "cakephp/datasource": "^4.0", + "php": ">=7.4.0" + }, + "suggest": { + "cakephp/i18n": "If you are using locale-aware datetime formats or Chronos types.", + "cakephp/log": "If you want to use query logging without providing a logger yourself." + }, + "type": "library", + "autoload": { + "psr-4": { + "Cake\\Database\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/database/graphs/contributors" + } + ], + "description": "Flexible and powerful Database abstraction library with a familiar PDO-like API", + "homepage": "https://cakephp.org", + "keywords": [ + "abstraction", + "cakephp", + "database", + "database abstraction", + "pdo" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/database" + }, + "time": "2023-12-07T12:23:54+00:00" + }, + { + "name": "cakephp/datasource", + "version": "4.5.7", + "source": { + "type": "git", + "url": "https://github.com/cakephp/datasource.git", + "reference": "5d11a35ffc09dee744faaab7f758aeb42c17cfec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/datasource/zipball/5d11a35ffc09dee744faaab7f758aeb42c17cfec", + "reference": "5d11a35ffc09dee744faaab7f758aeb42c17cfec", + "shasum": "" + }, + "require": { + "cakephp/core": "^4.0", + "php": ">=7.4.0", + "psr/log": "^1.0 || ^2.0", + "psr/simple-cache": "^1.0 || ^2.0" + }, + "suggest": { + "cakephp/cache": "If you decide to use Query caching.", + "cakephp/collection": "If you decide to use ResultSetInterface.", + "cakephp/utility": "If you decide to use EntityTrait." + }, + "type": "library", + "autoload": { + "psr-4": { + "Cake\\Datasource\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/datasource/graphs/contributors" + } + ], + "description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores", + "homepage": "https://cakephp.org", + "keywords": [ + "cakephp", + "connection management", + "datasource", + "entity", + "query" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/datasource" + }, + "time": "2023-11-05T07:32:10+00:00" + }, + { + "name": "cakephp/utility", + "version": "4.5.7", + "source": { + "type": "git", + "url": "https://github.com/cakephp/utility.git", + "reference": "708929115e5b400e1b5b76d8120ca2e51e2de199" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/utility/zipball/708929115e5b400e1b5b76d8120ca2e51e2de199", + "reference": "708929115e5b400e1b5b76d8120ca2e51e2de199", + "shasum": "" + }, + "require": { + "cakephp/core": "^4.0", + "php": ">=7.4.0" + }, + "suggest": { + "ext-intl": "To use Text::transliterate() or Text::slug()", + "lib-ICU": "To use Text::transliterate() or Text::slug()" + }, + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Cake\\Utility\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/utility/graphs/contributors" + } + ], + "description": "CakePHP Utility classes such as Inflector, String, Hash, and Security", + "homepage": "https://cakephp.org", + "keywords": [ + "cakephp", + "hash", + "inflector", + "security", + "string", + "utility" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/utility" + }, + "time": "2024-06-23T00:11:14+00:00" + }, { "name": "carbonphp/carbon-doctrine-types", "version": "3.2.0", @@ -432,6 +665,69 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v6.10.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "500501c2ce893c824c801da135d02661199f60c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", + "reference": "500501c2ce893c824c801da135d02661199f60c5", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + }, + "time": "2024-05-18T18:05:11+00:00" + }, { "name": "ilexn/swoole-convert-psr7", "version": "0.6.1", @@ -1939,6 +2235,56 @@ }, "time": "2023-04-11T06:14:47+00:00" }, + { + "name": "psr/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" + }, { "name": "psr/simple-cache", "version": "1.0.1", @@ -1990,6 +2336,92 @@ }, "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "robmorgan/phinx", + "version": "0.14.0", + "source": { + "type": "git", + "url": "https://github.com/cakephp/phinx.git", + "reference": "7bc24bae664b2124f3d5b8d1e98fdb8abaf70e87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/phinx/zipball/7bc24bae664b2124f3d5b8d1e98fdb8abaf70e87", + "reference": "7bc24bae664b2124f3d5b8d1e98fdb8abaf70e87", + "shasum": "" + }, + "require": { + "cakephp/database": "^4.0", + "php-64bit": ">=7.3", + "psr/container": "^1.0 || ^2.0", + "symfony/config": "^3.4|^4.0|^5.0|^6.0", + "symfony/console": "^3.4|^4.0|^5.0|^6.0" + }, + "require-dev": { + "cakephp/cakephp-codesniffer": "^4.0", + "ext-json": "*", + "ext-pdo": "*", + "phpunit/phpunit": "^9.5", + "sebastian/comparator": ">=1.2.3", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "ext-json": "Install if using JSON configuration format", + "ext-pdo": "PDO extension is needed", + "symfony/yaml": "Install if using YAML configuration format" + }, + "bin": [ + "bin/phinx" + ], + "type": "library", + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "https://robmorgan.id.au", + "role": "Lead Developer" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "https://shadowhand.me", + "role": "Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Developer" + }, + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/phinx/graphs/contributors", + "role": "Developer" + } + ], + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "homepage": "https://phinx.org", + "keywords": [ + "database", + "database migrations", + "db", + "migrations", + "phinx" + ], + "support": { + "issues": "https://github.com/cakephp/phinx/issues", + "source": "https://github.com/cakephp/phinx/tree/0.14.0" + }, + "time": "2023-09-07T14:26:14+00:00" + }, { "name": "symfony/clock", "version": "v7.1.1", @@ -2065,16 +2497,318 @@ "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/finder", - "version": "v7.1.4", + "name": "symfony/config", + "version": "v6.4.8", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + "url": "https://github.com/symfony/config.git", + "reference": "12e7e52515ce37191b193cf3365903c4f3951e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35", + "reference": "12e7e52515ce37191b193cf3365903c4f3951e35", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-20T08:15:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-17T09:16:35+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", "shasum": "" }, @@ -2289,6 +3023,163 @@ ], "time": "2024-09-20T08:28:38+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.31.0", @@ -2609,6 +3500,176 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/string", + "version": "v7.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-20T08:28:38+00:00" + }, { "name": "symfony/translation", "version": "v7.1.5", diff --git a/config/db/migrations/20241002030500_profile_table_migration.php b/config/db/migrations/20241002030500_profile_table_migration.php new file mode 100644 index 0000000..1a89dd8 --- /dev/null +++ b/config/db/migrations/20241002030500_profile_table_migration.php @@ -0,0 +1,41 @@ +hasTable('profile')) { + return; + } + + $this->table('profile') + ->addColumn('code', 'uuid', ['limit' => '255', 'default' => Literal::from('(UUID())')]) + ->addColumn('name', 'string', ['limit' => '255', 'null' => false]) + ->addColumn('active', 'boolean', ['default' => 1]) + ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP']) + ->addColumn('updated', 'timestamp', ['default' => 'CURRENT_TIMESTAMP']) + ->addIndex(['code']) + ->save(); + + $this->table('profile') + ->insert([ + ['name' => 'company'], + ['name' => 'customer'], + ]) + ->save(); + } + + public function down(): void + { + if (!$this->hasTable('profile')) { + return; + } + + $this->table('profile')->drop()->save(); + } +} diff --git a/config/db/migrations/20241002030501_user_table_migration.php b/config/db/migrations/20241002030501_user_table_migration.php new file mode 100644 index 0000000..6e170f2 --- /dev/null +++ b/config/db/migrations/20241002030501_user_table_migration.php @@ -0,0 +1,46 @@ +hasTable('user')) { + return; + } + + $this->table('user') + ->addColumn('profile_id', 'integer', ['signed' => false, 'null' => false]) + ->addColumn('code', 'uuid', ['limit' => '255', 'default' => Literal::from('(UUID())')]) + ->addColumn('email', 'string', ['limit' => '255', 'null' => false]) + ->addColumn('password', 'string', ['limit' => '255', 'null' => false]) + ->addColumn('name', 'string', ['limit' => '255', 'null' => false]) + ->addColumn('active', 'boolean', ['default' => 1]) + ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP']) + ->addColumn('updated', 'timestamp', ['default' => 'CURRENT_TIMESTAMP']) + ->addForeignKey(['profile_id'], 'profile', 'id', ['delete' => 'NO_ACTION', 'update' => 'NO_ACTION']) + ->addIndex(['code']) + ->addIndex(['email', 'password']) + ->create(); + + $this->table('user') + ->insert([ + ['profile_id' => 1, 'email' => 'company@master.com', 'password' => 'companymaster', 'name' => 'master company'], + ['profile_id' => 2, 'email' => 'customer@master.com', 'password' => 'customermaster', 'name' => 'master customer'], + ]) + ->save(); + } + + public function down(): void + { + if (!$this->hasTable('user')) { + return; + } + + $this->table('user')->drop()->save(); + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fb52536 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3.8" + +networks: + local: + driver: bridge + +services: + + mysql8.0.13: + container_name: fidelify-db + hostname: fidelify-db + image: mysql:8.0.13 + ports: + - "3306:3306" + volumes: + - ./.data/mysql:/var/lib/mysql + networks: + - local + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=fidelify + + php8.3fpm: + container_name: fidelify-api + hostname: fidelify-api + entrypoint: [ "sh", "-c", "php public/index.php" ] + build: + context: . + dockerfile: .devops/Dockerfile + ports: + - "8003:8003" + env_file: + - .env + extra_hosts: + - host.docker.internal:host-gateway + - localhost:host-gateway + volumes: + - ./:/api + networks: + - local + depends_on: + - mysql8.0.13 diff --git a/lang/en/validation.php b/lang/en/validation.php new file mode 100644 index 0000000..33a9711 --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,9 @@ + 'is mandatory', + 'integer' => 'should be a valid integer', + 'email' => 'should be a valid email', + 'regex' => 'should have only valid characters', + 'not_regex' => 'should not have invalid characters', +]; diff --git a/phinx.php b/phinx.php new file mode 100644 index 0000000..6d80646 --- /dev/null +++ b/phinx.php @@ -0,0 +1,39 @@ + [ + 'migrations' => '%%PHINX_CONFIG_DIR%%/config/db/migrations', + 'seeds' => '%%PHINX_CONFIG_DIR%%/config/db/seeds' + ], + 'environments' => [ + 'default_migration_table' => 'phinxlog', + 'default_environment' => 'development', + 'development' => [ + 'adapter' => $dbType, + 'host' => $dbHost, + 'name' => $dbName, + 'user' => $dbUser, + 'pass' => $dbPass, + 'port' => $dbPort, + 'charset' => 'utf8', + ], + 'production' => [ + 'adapter' => $dbType, + 'host' => $dbHost, + 'name' => $dbName, + 'user' => $dbUser, + 'pass' => $dbPass, + 'port' => $dbPort, + 'charset' => 'utf8', + ], + ], + 'version_order' => 'creation', +]; diff --git a/src/Adapters/Database/MysqlAdapter.php b/src/Adapters/Database/MysqlAdapter.php new file mode 100644 index 0000000..533c0f0 --- /dev/null +++ b/src/Adapters/Database/MysqlAdapter.php @@ -0,0 +1,61 @@ +pdo->prepare(query: $query); + + foreach ($params as $key => $value) { + $var = is_numeric(value: $value) ? (int) $value : (string) $value; + $type = is_numeric(value: $value) ? \PDO::PARAM_INT : \PDO::PARAM_STR; + + $statement->bindValue(param: $key, value: $var, type: $type); + } + + return $statement->execute(); + } + + public function select(string $query, array $params): array + { + $statement = $this->pdo->prepare(query: $query); + + foreach ($params as $key => $value) { + $var = is_numeric(value: $value) ? (int) $value : (string) $value; + $type = is_numeric(value: $value) ? \PDO::PARAM_INT : \PDO::PARAM_STR; + + $statement->bindValue(param: $key, value: $var, type: $type); + } + + $statement->execute(); + + if ($statement->rowCount() === 1) { + return $statement->fetch(mode: \PDO::FETCH_ASSOC); + } + + return $statement->fetchAll(mode: \PDO::FETCH_ASSOC); + } +} diff --git a/src/Adapters/Token/JwtAdapter.php b/src/Adapters/Token/JwtAdapter.php new file mode 100644 index 0000000..d32d96e --- /dev/null +++ b/src/Adapters/Token/JwtAdapter.php @@ -0,0 +1,32 @@ +secret, 'HS256'); + } + + public function decode(string $token): array + { + return (array) JWT::decode($token, new Key($this->secret, 'HS256')); + } +} diff --git a/src/Adapters/Validation/IlluminateAdapter.php b/src/Adapters/Validation/IlluminateAdapter.php new file mode 100644 index 0000000..9adf8d0 --- /dev/null +++ b/src/Adapters/Validation/IlluminateAdapter.php @@ -0,0 +1,39 @@ +factory->make(data: $data, rules: $rules); + + if ($validator->fails()) { + $errors = json_encode(value: $validator->errors()); + throw new \Exception(message: $errors, code: 422); + } + } +} diff --git a/src/Base.php b/src/Base.php deleted file mode 100644 index 8b86870..0000000 --- a/src/Base.php +++ /dev/null @@ -1,24 +0,0 @@ -getBody()->getContents(); - - if (empty($body)) { - return null; - } - - return json_decode($body, true); - } -} diff --git a/src/Main.php b/src/Main.php index 97bddf0..91dc870 100644 --- a/src/Main.php +++ b/src/Main.php @@ -15,17 +15,15 @@ class Main { public function __construct( - private Server $server - ) { - $this->server = $server; - } + private Server $server, + ) {} public static function create(): self { - $serverHost = is_string(getenv('SERVER_HOST')) ? getenv('SERVER_HOST') : '0.0.0.0'; - $serverPort = is_numeric(getenv('SERVER_PORT')) ? getenv('SERVER_PORT') : 8003; + $serverHost = is_string(value: getenv(name: 'SERVER_HOST')) ? getenv(name: 'SERVER_HOST') : '0.0.0.0'; + $serverPort = is_numeric(value: getenv(name: 'SERVER_PORT')) ? getenv(name: 'SERVER_PORT') : '8003'; - $server = new Server($serverHost, $serverPort); + $server = new Server(host: $serverHost, port: (int) $serverPort); return new static($server); } @@ -34,20 +32,23 @@ public function run(): void { $psr17Factory = new Psr17Factory; $requestConverter = new SwooleServerRequestConverter( - $psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory + serverRequestFactory: $psr17Factory, + uriFactory: $psr17Factory, + uploadedFileFactory: $psr17Factory, + streamFactory: $psr17Factory, ); $routes = Routes::create(); - $this->server->on('start', function (): void { + $this->server->on(event_name: 'start', callback: function (): void { echo '[fidelify-api] server started at http://127.0.0.1:8003' . PHP_EOL; }); - $this->server->on('request', function (Request $request, Response $response) use ($routes, $requestConverter) : void { - $psr7Request = $requestConverter->createFromSwoole($request); - $psr7Response = $routes->handle($psr7Request); - $converter = new SwooleResponseConverter($response); - $converter->send($psr7Response); + $this->server->on(event_name: 'request', callback: function (Request $request, Response $response) use ($routes, $requestConverter): void { + $psr7Request = $requestConverter->createFromSwoole(swooleRequest: $request); + $psr7Response = $routes->handle(request: $psr7Request); + $converter = new SwooleResponseConverter(response: $response); + $converter->send(response: $psr7Response); echo "[fidelify-api] {$psr7Response->getStatusCode()} {$psr7Response->getReasonPhrase()} {$psr7Response->getBody()}" . PHP_EOL; }); diff --git a/src/Modules/Auth/Controller/AuthController.php b/src/Modules/Auth/Controller/AuthController.php deleted file mode 100644 index 9f939df..0000000 --- a/src/Modules/Auth/Controller/AuthController.php +++ /dev/null @@ -1,74 +0,0 @@ -authService->signup($authRequest), - ); - - return new Response(json_encode([ - 'token' => $authResponse->token, - ]), 200); - } catch (\Throwable $th) { - return new Response(json_encode([ - 'error' => 'Fail to signup', - ]), 400); - } - } - - #[Route(method: 'POST', path: '/login')] - public function login(RequestInterface $request): ResponseInterface - { - try { - return new Response(json_encode([]), 200); - } catch (\Throwable $th) { - return new Response(json_encode([ - 'error' => 'Fail to login', - ]), 400); - } - } - - #[Route(method: 'POST', path: '/password')] - public function password(RequestInterface $request): ResponseInterface - { - try { - return new Response(json_encode([]), 200); - } catch (\Throwable $th) { - return new Response(json_encode([ - 'error' => 'Fail reset password', - ]), 400); - } - } -} diff --git a/src/Modules/Auth/Controllers/AuthController.php b/src/Modules/Auth/Controllers/AuthController.php index 5aebcff..16178ae 100644 --- a/src/Modules/Auth/Controllers/AuthController.php +++ b/src/Modules/Auth/Controllers/AuthController.php @@ -4,6 +4,11 @@ namespace Fidelify\Api\Modules\Auth\Controllers; +use Fidelify\Api\Modules\Auth\Entities\SignupRequestEntity; +use Fidelify\Api\Modules\Auth\Entities\SignupResponseEntity; +use Fidelify\Api\Modules\Auth\Services\AuthService; + +use Fidelify\Api\Modules\Util\Services\ValidationService; use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -13,52 +18,78 @@ class AuthController public static function signup(RequestInterface $request): ResponseInterface { try { - return new JsonResponse(json_encode([ - 'request' => $request->getBody()->getContents(), - ]), 200); + $json = json_decode(json: $request->getBody()->getContents(), associative: true); + + $validationService = ValidationService::create(); + $validationService->validate(data: $json, rules: [ + 'profileId' => 'required|integer', + 'name' => 'required|regex:/^[A-Za-z\s]+$/', + 'email' => 'required|email', + 'password' => 'required|not_regex:/[\(\)\[\]\{\}\<\>]/', + ]); + + $signupRequestEntity = new SignupRequestEntity( + profileId: $json['profileId'], + name: $json['name'], + email: $json['email'], + password: $json['password'], + ); + + $authService = AuthService::create(); + $token = $authService->signup(signupRequestEntity: $signupRequestEntity); + + $signupResponseEntity = new SignupResponseEntity( + token: $token, + ); + + return new JsonResponse(data: $signupResponseEntity, status: 200); } catch (\Throwable $th) { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'error' => 'Fail to signup', - ]), 400); + 'reason' => $th->getMessage(), + ], status: 400); } } public static function signin(RequestInterface $request): ResponseInterface { try { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'request' => $request->getBody()->getContents(), - ]), 200); + ], status: 200); } catch (\Throwable $th) { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'error' => 'Fail to signin', - ]), 400); + 'reason' => $th->getMessage(), + ], status: 400); } } public static function signout(RequestInterface $request): ResponseInterface { try { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'request' => $request->getBody()->getContents(), - ]), 200); + ], status: 200); } catch (\Throwable $th) { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'error' => 'Fail to signout', - ]), 400); + 'reason' => $th->getMessage(), + ], status: 400); } } public static function reset(RequestInterface $request): ResponseInterface { try { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'request' => $request->getBody()->getContents(), - ]), 200); + ], status: 200); } catch (\Throwable $th) { - return new JsonResponse(json_encode([ + return new JsonResponse(data: [ 'error' => 'Fail to reset credentials', - ]), 400); + 'reason' => $th->getMessage(), + ], status: 400); } } } diff --git a/src/Modules/Auth/Entities/SignupRequestEntity.php b/src/Modules/Auth/Entities/SignupRequestEntity.php new file mode 100644 index 0000000..54cd17e --- /dev/null +++ b/src/Modules/Auth/Entities/SignupRequestEntity.php @@ -0,0 +1,15 @@ +withStatus(401); + return (new Response())->withStatus(code: 401); } - return $handler->handle($request); + return $handler->handle(request: $request); } } diff --git a/src/Modules/Auth/Model/AuthModel.php b/src/Modules/Auth/Model/AuthModel.php deleted file mode 100644 index 3f89a12..0000000 --- a/src/Modules/Auth/Model/AuthModel.php +++ /dev/null @@ -1,27 +0,0 @@ -databaseAdapter->execute( + query: 'INSERT INTO user (profile_id, name, email, password) VALUES (:profileId, :name, :email, :password)', + params: [ + ':profileId' => $signupRequestEntity->profileId, + ':name' => $signupRequestEntity->name, + ':email' => $signupRequestEntity->email, + ':password' => $signupRequestEntity->password, + ], + ); + + if (!isset($insertResult) || empty($insertResult)) { + throw new \Exception(message: 'Fail to persist to database', code: 400); + } + + $selectResult = $this->databaseAdapter->select( + query: 'SELECT code, email FROM user WHERE id = :id', + params: ['id' => $insertResult], + ); + + if (!isset($selectResult) || empty($selectResult)) { + throw new \Exception(message: 'Fail to get from database', code: 400); + } + + $tokenResult = $this->tokenAdapter->encode(data: $selectResult); + + if (!isset($tokenResult) || empty($tokenResult)) { + throw new \Exception(message: 'Fail to generate token', code: 400); + } + + return $tokenResult; + } +} diff --git a/src/Modules/Auth/Service/AuthService.php b/src/Modules/Auth/Service/AuthService.php deleted file mode 100644 index e8f1d76..0000000 --- a/src/Modules/Auth/Service/AuthService.php +++ /dev/null @@ -1,15 +0,0 @@ -password = base64_encode(string: password_hash( + password: $signupRequestEntity->password, + algo: PASSWORD_BCRYPT, + )); + + return $this->repository->signup(signupRequestEntity: $signupRequestEntity); + } +} diff --git a/src/Modules/User/Controllers/UserController.php b/src/Modules/User/Controllers/UserController.php deleted file mode 100644 index 41be201..0000000 --- a/src/Modules/User/Controllers/UserController.php +++ /dev/null @@ -1,64 +0,0 @@ - $request->getBody()->getContents(), - ]), 200); - } catch (\Throwable $th) { - return new JsonResponse(json_encode([ - 'error' => 'Fail to list users', - ]), 400); - } - } - - public static function get(RequestInterface $request): ResponseInterface - { - try { - return new JsonResponse(json_encode([ - 'request' => $request->getBody()->getContents(), - ]), 200); - } catch (\Throwable $th) { - return new JsonResponse(json_encode([ - 'error' => 'Fail to get user', - ]), 400); - } - } - - public static function create(RequestInterface $request): ResponseInterface - { - try { - return new JsonResponse(json_encode([ - 'request' => $request->getBody()->getContents(), - ]), 200); - } catch (\Throwable $th) { - return new JsonResponse(json_encode([ - 'error' => 'Fail create user', - ]), 400); - } - } - - public static function update(RequestInterface $request): ResponseInterface - { - try { - return new JsonResponse(json_encode([ - 'request' => $request->getBody()->getContents(), - ]), 200); - } catch (\Throwable $th) { - return new JsonResponse(json_encode([ - 'error' => 'Fail update user', - ]), 400); - } - } -} diff --git a/src/Modules/Util/Services/ValidationService.php b/src/Modules/Util/Services/ValidationService.php new file mode 100644 index 0000000..c449e36 --- /dev/null +++ b/src/Modules/Util/Services/ValidationService.php @@ -0,0 +1,26 @@ +adapter->validate(data: $data, rules: $rules); + } +} diff --git a/src/Routes.php b/src/Routes.php index 0cc6f30..e62f23b 100644 --- a/src/Routes.php +++ b/src/Routes.php @@ -6,7 +6,7 @@ const DS = DIRECTORY_SEPARATOR; -use Laminas\Diactoros\Response; +use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ResponseFactory; use League\Route\RouteGroup; use League\Route\Router; @@ -16,69 +16,66 @@ use Fidelify\Api\Modules\Auth\Controllers\AuthController; use Fidelify\Api\Modules\Auth\Middlewares\AuthMiddleware; -use Fidelify\Api\Modules\User\Controllers\UserController; class Routes { public function __construct( - private Router $router - ) { - $this->router = $router; - } + private Router $router, + ) {} public static function create(): self { $responseFactory = new ResponseFactory(); - $strategy = new JsonStrategy($responseFactory); + $strategy = new JsonStrategy(responseFactory: $responseFactory); $router = new Router(); - $router->setStrategy($strategy); + $router->setStrategy(strategy: $strategy); return new static($router); } public function handle(RequestInterface $request): ResponseInterface { - $this->router->map('GET', '/', function (): ResponseInterface { - return new Response(json_encode([ + $this->router->map(method: 'GET', path: '/', handler: function (): ResponseInterface { + return new JsonResponse(data: [ 'title' => 'Fidelify API', - 'version' => file_get_contents(dirname(__DIR__) . DS . 'VERSION'), - ]), 200); + 'version' => trim(string: file_get_contents(filename: dirname(path: __DIR__) . DS . 'VERSION')), + ], status: 200); }); - $this->router->group('/auth', function (RouteGroup $route): void { - $route->map('POST', '/signup', [AuthController::class, 'signup']); - $route->map('POST', '/signin', [AuthController::class, 'signin']); - $route->map('POST', '/signout', [AuthController::class, 'signout']); - $route->map('POST', '/reset', [AuthController::class, 'reset']); + $this->router->group(prefix: '/auth', group: function (RouteGroup $route): void { + $route->map(method: 'POST', path: '/signup', handler: [AuthController::class, 'signup']); + $route->map(method: 'POST', path: '/signin', handler: [AuthController::class, 'signin']); + $route->map(method: 'POST', path: '/signout', handler: [AuthController::class, 'signout']); + $route->map(method: 'POST', path: '/reset', handler: [AuthController::class, 'reset']); }); - $this->router->group('/user', function (RouteGroup $route): void { - $route->map('GET', '/list', [UserController::class, 'list']); - $route->map('GET', '/{id}', [UserController::class, 'get']); - $route->map('POST', '/{id}', [UserController::class, 'create']); - $route->map('PUT', '/{id}', [UserController::class, 'update']); - })->middleware(new AuthMiddleware()); + // $this->router->group(prefix: '/user', group: function (RouteGroup $route): void { + // $route->map(method: 'GET', path: '/list', handler: [UserController::class, 'list']); + // $route->map(method: 'GET', path: '/{id}', handler: [UserController::class, 'get']); + // $route->map(method: 'POST', path: '/{id}', handler: [UserController::class, 'create']); + // $route->map(method: 'PUT', path: '/{id}', handler: [UserController::class, 'update']); + // })->middleware(middleware: new AuthMiddleware()); // $this->router->group('/company', function (RouteGroup $route): void { // $route->map('GET', '/list', [UserController::class, 'list']); // $route->map('GET', '/{id}', [UserController::class, 'get']); // $route->map('POST', '/{id}', [UserController::class, 'create']); // $route->map('PUT', '/{id}', [UserController::class, 'update']); - // })->middleware(new AuthMiddleware()); + // })->middleware(middleware: new AuthMiddleware()); // $this->router->group('/category', function (RouteGroup $route): void { // $route->map('GET', '/list', [UserController::class, 'list']); // $route->map('GET', '/{id}', [UserController::class, 'get']); // $route->map('POST', '/{id}', [UserController::class, 'create']); // $route->map('PUT', '/{id}', [UserController::class, 'update']); - // })->middleware(new AuthMiddleware()); + // })->middleware(middleware: new AuthMiddleware()); // $this->router->group('/product', function (RouteGroup $route): void { // $route->map('GET', '/list', [UserController::class, 'list']); // $route->map('GET', '/{id}', [UserController::class, 'get']); // $route->map('POST', '/{id}', [UserController::class, 'create']); // $route->map('PUT', '/{id}', [UserController::class, 'update']); - // })->middleware(new AuthMiddleware()); + // })->middleware(middleware: new AuthMiddleware()); // $this->router->group('/fidelity', function (RouteGroup $route): void { // $route->map('GET', '/list', [UserController::class, 'list']); @@ -86,12 +83,12 @@ public function handle(RequestInterface $request): ResponseInterface // $route->map('POST', '/{id}', [UserController::class, 'create']); // $route->map('PUT', '/{id}', [UserController::class, 'update']); // $route->map('POST', '/{id}/checkpoint', [UserController::class, 'checkpoint']); - // })->middleware(new AuthMiddleware()); + // })->middleware(middleware: new AuthMiddleware()); // $this->router->group('/dashboard', function (RouteGroup $route): void { // $route->map('GET', '/fidelity/list', [UserController::class, 'list']); // $route->map('GET', '/company/list', [UserController::class, 'list']); - // })->middleware(new AuthMiddleware()); + // })->middleware(middleware: new AuthMiddleware()); return $this->router->dispatch($request); }