├── .github └── workflows │ └── main.yml ├── .gitignore ├── .htrouter.php ├── .scrutinizer.yml_bak ├── LICENSE ├── README.md ├── api ├── config │ └── providers.php ├── controllers │ ├── BaseController.php │ ├── Companies │ │ ├── AddController.php │ │ └── GetController.php │ ├── IndividualTypes │ │ └── GetController.php │ ├── Individuals │ │ └── GetController.php │ ├── LoginController.php │ ├── ProductTypes │ │ └── GetController.php │ ├── Products │ │ └── GetController.php │ └── Users │ │ └── GetController.php └── public │ ├── .htaccess │ ├── favicon.ico │ └── index.php ├── cli ├── cli.php ├── config │ └── providers.php └── tasks │ ├── ClearcacheTask.php │ └── MainTask.php ├── codeception.yml ├── composer.json ├── composer.lock ├── docker-compose.yml ├── docker ├── 8.0 │ ├── Dockerfile │ └── config │ │ ├── .bashrc │ │ └── extra.ini └── 8.1 │ ├── Dockerfile │ └── config │ ├── .bashrc │ └── extra.ini ├── library ├── Bootstrap │ ├── AbstractBootstrap.php │ ├── Api.php │ ├── Cli.php │ └── Tests.php ├── Constants │ ├── Flags.php │ └── Relationships.php ├── Core │ ├── autoload.php │ ├── config.php │ └── functions.php ├── ErrorHandler.php ├── Exception │ ├── Exception.php │ ├── HttpException.php │ └── ModelException.php ├── Http │ ├── Request.php │ └── Response.php ├── Middleware │ ├── AuthenticationMiddleware.php │ ├── NotFoundMiddleware.php │ ├── ResponseMiddleware.php │ ├── TokenBase.php │ ├── TokenUserMiddleware.php │ ├── TokenValidationMiddleware.php │ └── TokenVerificationMiddleware.php ├── Models │ ├── Companies.php │ ├── CompaniesXProducts.php │ ├── IndividualTypes.php │ ├── Individuals.php │ ├── ProductTypes.php │ ├── Products.php │ └── Users.php ├── Mvc │ └── Model │ │ └── AbstractModel.php ├── Providers │ ├── CacheDataProvider.php │ ├── CliDispatcherProvider.php │ ├── ConfigProvider.php │ ├── DatabaseProvider.php │ ├── ErrorHandlerProvider.php │ ├── LoggerProvider.php │ ├── ModelsMetadataProvider.php │ ├── RequestProvider.php │ ├── ResponseProvider.php │ └── RouterProvider.php ├── Traits │ ├── FractalTrait.php │ ├── QueryTrait.php │ ├── ResponseTrait.php │ └── TokenTrait.php ├── Transformers │ ├── BaseTransformer.php │ ├── CompaniesTransformer.php │ ├── IndividualTypesTransformer.php │ ├── IndividualsTransformer.php │ ├── ProductTypesTransformer.php │ └── ProductsTransformer.php └── Validation │ └── CompaniesValidator.php ├── phinx.php ├── phpcs.xml ├── psalm.xml.dist ├── runCli ├── runTests ├── storage ├── cache │ ├── .gitignore │ ├── data │ │ └── .gitignore │ └── metadata │ │ └── .gitignore ├── ci │ ├── .env.example │ ├── .env.prod │ ├── .htaccess.txt │ ├── phinx.php.example │ ├── phinx.php.prod │ └── xdebug.ini ├── config │ ├── .env.ci │ ├── .env.test │ ├── extra.ini │ ├── my.cnf │ └── nginx │ │ ├── 8.0 │ │ └── app-8.0.conf │ │ └── 8.1 │ │ └── app-8.1.conf ├── db │ └── migrations │ │ ├── 20180508203845_add_users_table.php │ │ ├── 20180509232740_add_token_and_audience_fields_to_users.php │ │ ├── 20180510004748_add_token_id_in_users.php │ │ ├── 20180525210751_increase_token_size.php │ │ ├── 20180528011826_split_token_field.php │ │ ├── 20180604160513_add_token_password_in_users.php │ │ ├── 20180607165746_remove_token_fields_from_users.php │ │ ├── 20180612191624_rename_domain_to_issuer.php │ │ ├── 20180715202028_add_companies_table.php │ │ ├── 20180715221034_add_products_table.php │ │ ├── 20180717231009_add_product_types_table.php │ │ ├── 20180717231024_add_individuals_table.php │ │ ├── 20180717231029_add_individual_types_table.php │ │ ├── 20180717231052_add_companies_to_products_table.php │ │ ├── 20180717232102_add_product_type_to_products.php │ │ └── 20180723152114_rename_table_fields.php └── logs │ └── .gitignore └── tests ├── _bootstrap.php ├── _data └── dump.sql ├── _support ├── ApiTester.php ├── CliTester.php ├── Helper │ ├── Api.php │ ├── Cli.php │ ├── Integration.php │ └── Unit.php ├── IntegrationTester.php ├── Page │ └── Data.php ├── UnitTester.php └── _generated │ └── .gitignore ├── api.suite.yml ├── api ├── Companies │ ├── AddCest.php │ ├── GetBase.php │ ├── GetCest.php │ ├── GetFieldsCest.php │ ├── GetIncludesCest.php │ └── GetSortCest.php ├── IndividualTypes │ └── GetCest.php ├── Individuals │ └── GetCest.php ├── LoginCest.php ├── NotFoundCest.php ├── ProductTypes │ └── GetCest.php ├── Products │ └── GetCest.php ├── Users │ └── GetCest.php └── _bootstrap.php ├── cli.suite.yml ├── cli ├── CheckHelpTaskCest.php └── _bootstrap.php ├── integration.suite.yml ├── integration ├── _bootstrap.php └── library │ ├── ModelCest.php │ ├── Models │ ├── CompaniesCest.php │ ├── CompaniesXProductsCest.php │ ├── IndividualTypesCest.php │ ├── IndividualsCest.php │ ├── ProductTypesCest.php │ ├── ProductsCest.php │ └── UsersCest.php │ ├── Traits │ └── QueryCest.php │ ├── Transformers │ ├── BaseTransformerCest.php │ ├── IndividualsTransformerCest.php │ └── ProductsTransformerCest.php │ └── Validation │ └── CompaniesValidatorCest.php ├── unit.suite.yml └── unit ├── BootstrapCest.php ├── _bootstrap.php ├── cli ├── BaseCest.php ├── BootstrapCest.php └── ClearCacheCest.php ├── config ├── AutoloaderCest.php ├── ConfigCest.php ├── FunctionsCest.php └── ProvidersCest.php └── library ├── BootstrapCest.php ├── Constants ├── FlagsCest.php └── RelationshipsCest.php ├── ErrorHandlerCest.php ├── Http └── ResponseCest.php └── Providers ├── CacheCest.php ├── ConfigCest.php ├── DatabaseCest.php ├── ErrorHandlerCest.php ├── LoggerCest.php ├── ModelsMetadataCest.php ├── ResponseCest.php └── RouterCest.php /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Testing Suite 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | DATA_API_MYSQL_HOST: '127.0.0.1' 7 | DATA_API_REDIS_HOST: '127.0.0.1' 8 | 9 | jobs: 10 | run: 11 | runs-on: ubuntu-latest 12 | name: Workflow - PHP-${{ matrix.php }} 13 | 14 | services: 15 | mysql: 16 | image: mysql:5.7 17 | ports: 18 | - "3306:3306" 19 | env: 20 | MYSQL_ROOT_PASSWORD: secret 21 | MYSQL_USER: phalcon 22 | MYSQL_DATABASE: phalcon_api 23 | MYSQL_PASSWORD: secret 24 | redis: 25 | image: redis:5-alpine 26 | ports: 27 | - "6379:6379" 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | php: ['8.0', '8.1' ] 33 | 34 | steps: 35 | - uses: actions/checkout@v1 36 | - name: Setup PHP 37 | uses: shivammathur/setup-php@v2 38 | with: 39 | php-version: ${{ matrix.php }} 40 | tools: pecl 41 | extensions: mbstring, intl, json, phalcon-5.0.0RC4 42 | coverage: xdebug 43 | 44 | - name: Init Database 45 | run: | 46 | mysql -uroot -h127.0.0.1 -psecret -e 'CREATE DATABASE IF NOT EXISTS `phalcon_api`;' 47 | 48 | - name: Validate composer.json and composer.lock 49 | run: composer validate 50 | 51 | - name: Get composer cache directory 52 | id: composer-cache 53 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 54 | 55 | - name: Cache dependencies 56 | uses: actions/cache@v2 57 | with: 58 | path: ${{ steps.composer-cache.outputs.dir }} 59 | key: ${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} 60 | restore-keys: ${{ matrix.php }}-composer- 61 | 62 | - name: Install Composer dependencies 63 | run: composer install --prefer-dist --no-progress --no-suggest 64 | 65 | - name: Run PHPCS 66 | if: always() 67 | run: vendor/bin/phpcs 68 | 69 | - name: Env file 70 | if: always() 71 | run: cp -v ./storage/config/.env.ci ./.env 72 | 73 | - name: Run migrations 74 | if: always() 75 | run: | 76 | vendor/bin/phinx migrate 77 | 78 | - name: Run tests 79 | if: always() 80 | run: | 81 | sudo php -S 0.0.0.0 -t ./.htrouter.php & 82 | vendor/bin/codecept build 83 | vendor/bin/codecept run unit --coverage-xml=unit-coverage.xml 84 | vendor/bin/codecept run integration --coverage-xml=integration-coverage.xml 85 | vendor/bin/codecept run cli --coverage-xml=cli-coverage.xml 86 | # vendor/bin/codecept run api --coverage-xml=api-coverage.xml 87 | 88 | - name: Upload to codecov 89 | uses: codecov/codecov-action@v3 90 | with: 91 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 92 | directory: ./tests/_output/ 93 | files: unit-coverage.xml,integration-coverage.xml,cli-coverage.xml 94 | name: codecov-umbrella # optional 95 | fail_ci_if_error: false 96 | verbose: true # optional (default = false) 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Please do not use this ignore file to define platform specific files. 2 | # 3 | # For these purposes create a global .gitignore file, which is a list of rules 4 | # for ignoring files in every Git repository on your computer. 5 | # 6 | # https://help.github.com/articles/ignoring-files/#create-a-global-gitignore 7 | 8 | .env 9 | tests/_output/ 10 | vendor/ 11 | -------------------------------------------------------------------------------- /.htrouter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | use Phalcon\Api\Providers\CacheDataProvider; 15 | use Phalcon\Api\Providers\ConfigProvider; 16 | use Phalcon\Api\Providers\DatabaseProvider; 17 | use Phalcon\Api\Providers\ErrorHandlerProvider; 18 | use Phalcon\Api\Providers\LoggerProvider; 19 | use Phalcon\Api\Providers\ModelsMetadataProvider; 20 | use Phalcon\Api\Providers\RequestProvider; 21 | use Phalcon\Api\Providers\ResponseProvider; 22 | use Phalcon\Api\Providers\RouterProvider; 23 | 24 | /** 25 | * Enabled providers. Order does matter 26 | */ 27 | return [ 28 | ConfigProvider::class, 29 | LoggerProvider::class, 30 | ErrorHandlerProvider::class, 31 | DatabaseProvider::class, 32 | ModelsMetadataProvider::class, 33 | RequestProvider::class, 34 | ResponseProvider::class, 35 | RouterProvider::class, 36 | CacheDataProvider::class, 37 | ]; 38 | -------------------------------------------------------------------------------- /api/controllers/Companies/AddController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\Companies; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Exception\ModelException; 18 | use Phalcon\Api\Http\Response; 19 | use Phalcon\Api\Models\Companies; 20 | use Phalcon\Api\Traits\FractalTrait; 21 | use Phalcon\Api\Transformers\BaseTransformer; 22 | use Phalcon\Api\Validation\CompaniesValidator; 23 | use Phalcon\Filter\Filter; 24 | use Phalcon\Mvc\Controller; 25 | 26 | use function Phalcon\Api\Core\appUrl; 27 | 28 | /** 29 | * Class AddController 30 | * 31 | * @property Response $response 32 | */ 33 | class AddController extends Controller 34 | { 35 | use FractalTrait; 36 | 37 | /** 38 | * Adds a record in the database 39 | * 40 | * @throws ModelException 41 | */ 42 | public function callAction() 43 | { 44 | $validator = new CompaniesValidator(); 45 | $messages = $validator->validate($this->request->getPost()); 46 | 47 | /** 48 | * If no messages are returned, go ahead with the query 49 | */ 50 | if (0 === count($messages)) { 51 | $name = $this->request->getPost('name', Filter::FILTER_STRING); 52 | $address = $this->request->getPost('address', Filter::FILTER_STRING, ''); 53 | $city = $this->request->getPost('city', Filter::FILTER_STRING, ''); 54 | $phone = $this->request->getPost('phone', Filter::FILTER_STRING, ''); 55 | 56 | $company = new Companies(); 57 | $result = $company 58 | ->set('name', $name) 59 | ->set('address', $address) 60 | ->set('city', $city) 61 | ->set('phone', $phone) 62 | ->save() 63 | ; 64 | 65 | if (false !== $result) { 66 | $data = $this->format( 67 | 'item', 68 | $company, 69 | BaseTransformer::class, 70 | 'companies' 71 | ); 72 | 73 | $this 74 | ->response 75 | ->setHeader('Location', appUrl(Relationships::COMPANIES, $company->get('id'))) 76 | ->setJsonContent($data) 77 | ->setStatusCode($this->response::CREATED) 78 | ; 79 | } else { 80 | /** 81 | * Errors happened store them 82 | */ 83 | $this 84 | ->response 85 | ->setPayloadErrors($company->getMessages()) 86 | ; 87 | } 88 | } else { 89 | /** 90 | * Set the errors in the payload 91 | */ 92 | $this 93 | ->response 94 | ->setPayloadErrors($messages) 95 | ; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /api/controllers/Companies/GetController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\Companies; 15 | 16 | use Phalcon\Api\Api\Controllers\BaseController; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\Companies; 19 | use Phalcon\Api\Transformers\CompaniesTransformer; 20 | 21 | /** 22 | * Class GetController 23 | */ 24 | class GetController extends BaseController 25 | { 26 | /** @var string */ 27 | protected string $model = Companies::class; 28 | 29 | /** @var array */ 30 | protected array $includes = [ 31 | Relationships::INDIVIDUALS, 32 | Relationships::PRODUCTS, 33 | ]; 34 | 35 | /** @var string */ 36 | protected string $resource = Relationships::COMPANIES; 37 | 38 | /** @var array */ 39 | protected array $sortFields = [ 40 | 'id' => true, 41 | 'name' => true, 42 | 'address' => true, 43 | 'city' => true, 44 | 'phone' => true, 45 | ]; 46 | 47 | /** @var string */ 48 | protected string $transformer = CompaniesTransformer::class; 49 | } 50 | -------------------------------------------------------------------------------- /api/controllers/IndividualTypes/GetController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\IndividualTypes; 15 | 16 | use Phalcon\Api\Api\Controllers\BaseController; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\IndividualTypes; 19 | use Phalcon\Api\Transformers\IndividualTypesTransformer; 20 | 21 | /** 22 | * Class GetController 23 | */ 24 | class GetController extends BaseController 25 | { 26 | /** @var string */ 27 | protected string $model = IndividualTypes::class; 28 | 29 | /** @var array */ 30 | protected array $includes = [ 31 | Relationships::INDIVIDUALS, 32 | ]; 33 | 34 | /** @var string */ 35 | protected string $resource = Relationships::INDIVIDUAL_TYPES; 36 | 37 | /** @var array */ 38 | protected array $sortFields = [ 39 | 'id' => true, 40 | 'name' => true, 41 | 'description' => false, 42 | ]; 43 | 44 | /** @var string */ 45 | protected string $transformer = IndividualTypesTransformer::class; 46 | } 47 | -------------------------------------------------------------------------------- /api/controllers/Individuals/GetController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\Individuals; 15 | 16 | use Phalcon\Api\Api\Controllers\BaseController; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\Individuals; 19 | use Phalcon\Api\Transformers\IndividualsTransformer; 20 | 21 | /** 22 | * Class GetController 23 | */ 24 | class GetController extends BaseController 25 | { 26 | /** @var string */ 27 | protected string $model = Individuals::class; 28 | 29 | /** @var array */ 30 | protected array $includes = [ 31 | Relationships::COMPANIES, 32 | Relationships::INDIVIDUAL_TYPES, 33 | ]; 34 | 35 | /** @var string */ 36 | protected string $resource = Relationships::INDIVIDUALS; 37 | 38 | /** @var string */ 39 | protected string $transformer = IndividualsTransformer::class; 40 | 41 | /** @var array */ 42 | protected array $sortFields = [ 43 | 'id' => true, 44 | 'companyId' => true, 45 | 'typeId' => true, 46 | 'prefix' => true, 47 | 'first' => true, 48 | 'middle' => true, 49 | 'last' => true, 50 | 'suffix' => true, 51 | ]; 52 | 53 | /** @var string */ 54 | protected string $orderBy = 'last, first'; 55 | } 56 | -------------------------------------------------------------------------------- /api/controllers/LoginController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers; 15 | 16 | use Phalcon\Api\Exception\ModelException; 17 | use Phalcon\Api\Http\Request; 18 | use Phalcon\Api\Http\Response; 19 | use Phalcon\Api\Models\Users; 20 | use Phalcon\Api\Traits\QueryTrait; 21 | use Phalcon\Api\Traits\TokenTrait; 22 | use Phalcon\Cache\Cache; 23 | use Phalcon\Config\Config; 24 | use Phalcon\Filter\Filter; 25 | use Phalcon\Mvc\Controller; 26 | 27 | /** 28 | * Class LoginController 29 | * 30 | * @property Cache $cache 31 | * @property Config $config 32 | * @property Request $request 33 | * @property Response $response 34 | */ 35 | class LoginController extends Controller 36 | { 37 | use TokenTrait; 38 | use QueryTrait; 39 | 40 | /** 41 | * Default action logging in 42 | * 43 | * @return void 44 | * @throws ModelException 45 | */ 46 | public function callAction() 47 | { 48 | $username = $this->request->getPost('username', Filter::FILTER_STRING); 49 | $password = $this->request->getPost('password', Filter::FILTER_STRING); 50 | 51 | /** @var Users|null $user */ 52 | $user = $this->getUserByUsernameAndPassword( 53 | $this->config, 54 | $this->cache, 55 | $username, 56 | $password 57 | ); 58 | 59 | if (null !== $user) { 60 | $this 61 | ->response 62 | ->setPayloadSuccess(['token' => $user->getToken()]) 63 | ; 64 | } else { 65 | $this 66 | ->response 67 | ->setPayloadError('Incorrect credentials') 68 | ; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /api/controllers/ProductTypes/GetController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\ProductTypes; 15 | 16 | use Phalcon\Api\Api\Controllers\BaseController; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\ProductTypes; 19 | use Phalcon\Api\Transformers\ProductTypesTransformer; 20 | 21 | /** 22 | * Class GetController 23 | */ 24 | class GetController extends BaseController 25 | { 26 | /** @var string */ 27 | protected string $model = ProductTypes::class; 28 | 29 | /** @var array */ 30 | protected array $includes = [ 31 | Relationships::PRODUCTS, 32 | ]; 33 | 34 | /** @var string */ 35 | protected string $resource = Relationships::PRODUCT_TYPES; 36 | 37 | /** @var array */ 38 | protected array $sortFields = [ 39 | 'id' => true, 40 | 'name' => true, 41 | 'description' => false, 42 | ]; 43 | 44 | /** @var string */ 45 | protected string $transformer = ProductTypesTransformer::class; 46 | } 47 | -------------------------------------------------------------------------------- /api/controllers/Products/GetController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\Products; 15 | 16 | use Phalcon\Api\Api\Controllers\BaseController; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\Products; 19 | use Phalcon\Api\Transformers\ProductsTransformer; 20 | 21 | /** 22 | * Class GetController 23 | */ 24 | class GetController extends BaseController 25 | { 26 | /** @var string */ 27 | protected string $model = Products::class; 28 | 29 | /** @var array */ 30 | protected array $includes = [ 31 | Relationships::COMPANIES, 32 | Relationships::PRODUCT_TYPES, 33 | ]; 34 | 35 | /** @var string */ 36 | protected string $resource = Relationships::PRODUCTS; 37 | 38 | /** @var array */ 39 | protected array $sortFields = [ 40 | 'id' => true, 41 | 'typeId' => true, 42 | 'name' => true, 43 | 'description' => false, 44 | 'quantity' => true, 45 | 'price' => true, 46 | ]; 47 | 48 | /** @var string */ 49 | protected string $transformer = ProductsTransformer::class; 50 | } 51 | -------------------------------------------------------------------------------- /api/controllers/Users/GetController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Api\Controllers\Users; 15 | 16 | use Phalcon\Api\Api\Controllers\BaseController; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\Users; 19 | use Phalcon\Api\Transformers\BaseTransformer; 20 | 21 | /** 22 | * Class GetController 23 | */ 24 | class GetController extends BaseController 25 | { 26 | /** @var string */ 27 | protected string $model = Users::class; 28 | 29 | /** @var string */ 30 | protected string $resource = Relationships::USERS; 31 | 32 | /** @var string */ 33 | protected string $transformer = BaseTransformer::class; 34 | 35 | /** @var array */ 36 | protected array $sortFields = [ 37 | 'id' => true, 38 | 'status' => true, 39 | 'username' => true, 40 | 'password' => false, 41 | 'issuer' => true, 42 | 'tokenPassword' => false, 43 | 'tokenId' => false, 44 | ]; 45 | 46 | /** @var string */ 47 | protected string $orderBy = 'username'; 48 | } 49 | -------------------------------------------------------------------------------- /api/public/.htaccess: -------------------------------------------------------------------------------- 1 | AddDefaultCharset UTF-8 2 | 3 | 4 | RewriteEngine On 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L] 8 | 9 | -------------------------------------------------------------------------------- /api/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phalcon/rest-api/a28464ff1c16c30c66cb744b3ec58d7de72371aa/api/public/favicon.ico -------------------------------------------------------------------------------- /api/public/index.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view 10 | * the LICENSE file that was distributed with this source code. 11 | */ 12 | 13 | use Phalcon\Api\Bootstrap\Api; 14 | 15 | require_once __DIR__ . '/../../library/Core/autoload.php'; 16 | 17 | (new Api())->run(); 18 | -------------------------------------------------------------------------------- /cli/cli.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view 10 | * the LICENSE file that was distributed with this source code. 11 | */ 12 | 13 | use Phalcon\Api\Bootstrap\Cli; 14 | 15 | require_once __DIR__ . '/../library/Core/autoload.php'; 16 | 17 | (new Cli())->run(); 18 | -------------------------------------------------------------------------------- /cli/config/providers.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | use Phalcon\Api\Providers\CacheDataProvider; 15 | use Phalcon\Api\Providers\CliDispatcherProvider; 16 | use Phalcon\Api\Providers\ConfigProvider; 17 | use Phalcon\Api\Providers\DatabaseProvider; 18 | use Phalcon\Api\Providers\ErrorHandlerProvider; 19 | use Phalcon\Api\Providers\LoggerProvider; 20 | use Phalcon\Api\Providers\ModelsMetadataProvider; 21 | 22 | /** 23 | * Enabled providers. Order does matter 24 | */ 25 | return [ 26 | ConfigProvider::class, 27 | LoggerProvider::class, 28 | ErrorHandlerProvider::class, 29 | DatabaseProvider::class, 30 | ModelsMetadataProvider::class, 31 | CliDispatcherProvider::class, 32 | CacheDataProvider::class, 33 | ]; 34 | -------------------------------------------------------------------------------- /cli/tasks/ClearcacheTask.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view 10 | * the LICENSE file that was distributed with this source code. 11 | */ 12 | 13 | namespace Phalcon\Api\Cli\Tasks; 14 | 15 | use Phalcon\Cache\Cache; 16 | use Phalcon\Cli\Task as PhTask; 17 | use Phalcon\Config\Config; 18 | use RecursiveDirectoryIterator; 19 | use RecursiveIteratorIterator; 20 | 21 | use Redis; 22 | 23 | use function in_array; 24 | use function Phalcon\Api\Core\appPath; 25 | 26 | use const PHP_EOL; 27 | 28 | /** 29 | * Class ClearcacheTask 30 | * 31 | * @property Cache $cache 32 | * @property Config $config 33 | */ 34 | class ClearcacheTask extends PhTask 35 | { 36 | /** 37 | * Clears the data cache from the application 38 | */ 39 | public function mainAction() 40 | { 41 | $this->clearFileCache(); 42 | $this->clearRedis(); 43 | } 44 | 45 | /** 46 | * Clears file based cache 47 | */ 48 | private function clearFileCache() 49 | { 50 | echo 'Clearing Cache folders' . PHP_EOL; 51 | 52 | $fileList = []; 53 | $whitelist = ['.', '..', '.gitignore']; 54 | $path = appPath('storage/cache'); 55 | $dirIterator = new RecursiveDirectoryIterator($path); 56 | $iterator = new RecursiveIteratorIterator( 57 | $dirIterator, 58 | RecursiveIteratorIterator::CHILD_FIRST 59 | ); 60 | 61 | /** 62 | * Get how many files we have there and where they are 63 | */ 64 | foreach ($iterator as $file) { 65 | if (true !== $file->isDir() && true !== in_array($file->getFilename(), $whitelist)) { 66 | $fileList[] = $file->getPathname(); 67 | } 68 | } 69 | 70 | echo sprintf('Found %s files', count($fileList)) . PHP_EOL; 71 | foreach ($fileList as $file) { 72 | echo '.'; 73 | unlink($file); 74 | } 75 | 76 | echo PHP_EOL . 'Cleared Cache folders' . PHP_EOL; 77 | } 78 | 79 | /** 80 | * Clears redis data cache 81 | */ 82 | private function clearRedis() 83 | { 84 | echo 'Clearing data cache' . PHP_EOL; 85 | 86 | $options = $this->config->path('cache') 87 | ->toArray() 88 | ; 89 | $redis = new Redis(); 90 | $redis->connect( 91 | $options['options']['host'], 92 | $options['options']['port'] 93 | ); 94 | 95 | $keys = $redis->keys("*"); 96 | $keys = $keys ?: []; 97 | echo sprintf('Found %s keys', count($keys)) . PHP_EOL; 98 | foreach ($keys as $key) { 99 | if ('api-data' === substr($key, 0, 8)) { 100 | $result = $redis->del($key); 101 | if ($result > 0) { 102 | echo '.'; 103 | } else { 104 | echo 'F'; 105 | } 106 | } 107 | } 108 | 109 | echo PHP_EOL . 'Cleared data cache' . PHP_EOL; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /cli/tasks/MainTask.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view 10 | * the LICENSE file that was distributed with this source code. 11 | */ 12 | 13 | namespace Phalcon\Api\Cli\Tasks; 14 | 15 | use Phalcon\Cli\Task as PhTask; 16 | 17 | use const PHP_EOL; 18 | 19 | class MainTask extends PhTask 20 | { 21 | /** 22 | * Executes the main action of the cli mapping passed parameters to tasks 23 | */ 24 | public function mainAction() 25 | { 26 | // 'green' => "\033[0;32m(%s)\033[0m", 27 | // 'red' => "\033[0;31m(%s)\033[0m", 28 | $year = date('Y'); 29 | $output = "" // Here just for readability 30 | . "******************************************************" . PHP_EOL 31 | . " Phalcon Team | (C) {$year}" . PHP_EOL 32 | . "******************************************************" . PHP_EOL 33 | . "" . PHP_EOL 34 | . "Usage: runCli " . PHP_EOL 35 | . "" . PHP_EOL 36 | . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL 37 | . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL 38 | . PHP_EOL; 39 | 40 | echo $output; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | # Can be changed while bootstrapping project 2 | actor_suffix: Tester 3 | 4 | paths: 5 | # Where the modules stored 6 | tests: tests 7 | output: tests/_output 8 | # Directory for fixture data 9 | data: tests/_data 10 | # Directory for custom modules (helpers) 11 | support: tests/_support 12 | envs: tests/_envs 13 | 14 | # The name of bootstrap that will be used. 15 | # Each bootstrap file should be inside a suite directory. 16 | bootstrap: _bootstrap.php 17 | 18 | settings: 19 | colors: true 20 | # Tests (especially functional) can take a lot of memory 21 | # We set a high limit for them by default. 22 | memory_limit: 128M 23 | log: true 24 | 25 | coverage: 26 | enabled: true 27 | remote: false 28 | include: 29 | - ./*.php 30 | exclude: 31 | - phinx.php 32 | - storage/* 33 | - tests/* 34 | - vendor/* 35 | 36 | extensions: 37 | enabled: 38 | - Codeception\Extension\RunFailed # default extension 39 | 40 | # Global modules configuration. 41 | modules: 42 | config: 43 | Phalcon: 44 | bootstrap: "tests/_ci/bootstrap.php" 45 | cleanup: false 46 | savepoints: false 47 | DB: 48 | dsn: 'mysql:host=%DATA_API_MYSQL_HOST%;dbname=%DATA_API_MYSQL_NAME%' 49 | user: '%DATA_API_MYSQL_USER%' 50 | password: '%DATA_API_MYSQL_PASS%' 51 | dump: 'tests/_data/dump.sql' 52 | populate: false 53 | cleanup: false 54 | reconnect: true 55 | 56 | # Get params from .env file 57 | params: 58 | - .env 59 | 60 | error_level: "E_ALL" 61 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phalcon/rest-api", 3 | "type": "library", 4 | "description": "This is a sample API REST application for the Phalcon PHP Framework", 5 | "keywords": [ 6 | "phalcon", 7 | "framework", 8 | "sample app", 9 | "rest-api", 10 | "rest", 11 | "api" 12 | ], 13 | "homepage": "https://phalcon.io", 14 | "license": "BSD-3-Clause", 15 | "authors": [ 16 | { 17 | "name": "Contributors", 18 | "homepage": "https://github.com/phalcon/rest-api/graphs/contributors" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=8.0", 23 | "ext-json": "*", 24 | "ext-openssl": "*", 25 | "ext-phalcon": "^5.0.0RC4", 26 | "league/fractal": "^0.20.1", 27 | "robmorgan/phinx": "^0.12.12", 28 | "vlucas/phpdotenv": "^5.4" 29 | }, 30 | "require-dev": { 31 | "codeception/codeception": "^5.0", 32 | "codeception/module-asserts": "^3.0", 33 | "codeception/module-cli": "^2.0", 34 | "codeception/module-filesystem": "^3.0", 35 | "codeception/module-phalcon5": "^2.0", 36 | "codeception/module-phpbrowser": "^3.0", 37 | "codeception/module-rest": "^3.3", 38 | "phalcon/ide-stubs": "^v5.0.0-RC1", 39 | "phpstan/phpstan": "^1.8", 40 | "squizlabs/php_codesniffer": "^3.7", 41 | "vimeo/psalm": "^4.27" 42 | }, 43 | "config": { 44 | "preferred-install": "dist", 45 | "sort-packages": true, 46 | "optimize-autoloader": true 47 | }, 48 | "scripts": { 49 | "phpcs": "phpcs --standard=PSR12" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | # Application - REST API 8.0 5 | application-8.0: 6 | build: docker/8.0 7 | container_name: rest-api-app-8.0 8 | restart: unless-stopped 9 | tty: true 10 | working_dir: /var/www 11 | depends_on: 12 | - nginx-8.0 13 | - redis 14 | - mysql 15 | volumes: 16 | - ./:/var/www 17 | - ./storage/config/extra.ini:/usr/local/etc/php/conf.d/extra.ini 18 | networks: 19 | - rest-api-network 20 | 21 | # Application - REST API 8.1 22 | application-8.1: 23 | build: docker/8.1 24 | container_name: rest-api-app-8.1 25 | restart: unless-stopped 26 | tty: true 27 | working_dir: /var/www 28 | depends_on: 29 | - nginx-8.1 30 | - redis 31 | - mysql 32 | volumes: 33 | - ./:/var/www 34 | - ./storage/config/extra.ini:/usr/local/etc/php/conf.d/extra.ini 35 | networks: 36 | - rest-api-network 37 | 38 | # Webserver - nginX 8.0 39 | nginx-8.0: 40 | image: nginx:alpine 41 | container_name: rest-api-nginx-8.0 42 | restart: unless-stopped 43 | tty: true 44 | volumes: 45 | - ./:/var/www 46 | - ./storage/config/nginx/8.0/:/etc/nginx/conf.d/ 47 | networks: 48 | - rest-api-network 49 | 50 | # Webserver - nginX 8.0 51 | nginx-8.1: 52 | image: nginx:alpine 53 | container_name: rest-api-nginx-8.1 54 | restart: unless-stopped 55 | tty: true 56 | volumes: 57 | - ./:/var/www 58 | - ./storage/config/nginx/8.1/:/etc/nginx/conf.d/ 59 | networks: 60 | - rest-api-network 61 | 62 | # Database - Mysql 63 | mysql: 64 | image: mysql:5.7.22 65 | container_name: rest-api-mysql 66 | restart: unless-stopped 67 | tty: true 68 | environment: 69 | - MYSQL_ROOT_PASSWORD=secret 70 | - MYSQL_USER=phalcon 71 | - MYSQL_DATABASE=phalcon_api 72 | - MYSQL_PASSWORD=secret 73 | volumes: 74 | - rest-api-volume:/var/lib/mysql/ 75 | - ./storage/config/my.cnf:/etc/mysql/my.cnf 76 | networks: 77 | - rest-api-network 78 | 79 | # Cache - Redis 80 | redis: 81 | container_name: rest-api-cache 82 | image: redis:5-alpine 83 | restart: "always" 84 | networks: 85 | - rest-api-network 86 | 87 | # Network 88 | networks: 89 | rest-api-network: 90 | driver: bridge 91 | 92 | # Volumes 93 | volumes: 94 | rest-api-volume: 95 | driver: local 96 | -------------------------------------------------------------------------------- /docker/8.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM composer:latest as composer 2 | FROM php:8.0-fpm 3 | 4 | # Set working directory 5 | WORKDIR /var/www 6 | 7 | LABEL vendor="Phalcon" \ 8 | maintainer="Phalcon Team " \ 9 | description="The PHP image to test the REST API example concepts" 10 | 11 | ENV PHALCON_VERSION="5.0.1" \ 12 | PHP_VERSION="8.0" 13 | 14 | # Update 15 | RUN apt update -y && \ 16 | apt install -y \ 17 | apt-utils \ 18 | gettext \ 19 | git \ 20 | libzip-dev \ 21 | nano \ 22 | sudo \ 23 | wget \ 24 | zip 25 | 26 | # PECL Packages 27 | RUN pecl install -o -f redis && \ 28 | pecl install phalcon-${PHALCON_VERSION} \ 29 | xdebug 30 | 31 | # Install PHP extensions 32 | RUN docker-php-ext-install \ 33 | gettext \ 34 | pdo_mysql \ 35 | zip 36 | 37 | # Install PHP extensions 38 | RUN docker-php-ext-enable \ 39 | opcache \ 40 | phalcon \ 41 | redis \ 42 | xdebug 43 | 44 | # Clear cache 45 | RUN apt-get clean && rm -rf /var/lib/apt/lists/* 46 | 47 | # Add user 48 | RUN groupadd -g 1000 phalcon 49 | RUN useradd -u 1000 -ms /bin/bash -g phalcon phalcon 50 | 51 | # Composer 52 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 53 | 54 | # Copy existing application directory contents 55 | COPY . /var/www 56 | 57 | # Bash script with helper aliases 58 | COPY ./config/.bashrc /root/.bashrc 59 | COPY ./config/.bashrc /home/phalcon/.bashrc 60 | 61 | # Copy existing application directory permissions 62 | COPY --chown=phalcon:phalcon . /var/www 63 | 64 | # Change current user to phalcon 65 | USER phalcon 66 | 67 | # Expose port 9000 and start php-fpm server 68 | EXPOSE 9000 69 | 70 | CMD ["php-fpm"] 71 | -------------------------------------------------------------------------------- /docker/8.0/config/.bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Easier navigation: .., ..., ...., ....., ~ and - 4 | alias ..="cd .." 5 | alias ...="cd ../.." 6 | alias ....="cd ../../.." 7 | alias .....="cd ../../../.." 8 | alias ~="cd ~" # `cd` is probably faster to type though 9 | alias -- -="cd -" 10 | 11 | # Shortcuts 12 | alias g="git" 13 | alias h="history" 14 | 15 | # Detect which `ls` flavor is in use 16 | if ls --color > /dev/null 2>&1; then # GNU `ls` 17 | colorflag="--color" 18 | else # OS X `ls` 19 | colorflag="-G" 20 | fi 21 | 22 | # List all files colorized in long format 23 | # shellcheck disable=SC2139 24 | alias l="ls -lF ${colorflag}" 25 | 26 | # List all files colorized in long format, including dot files 27 | # shellcheck disable=SC2139 28 | alias la="ls -laF ${colorflag}" 29 | 30 | # List only directories 31 | # shellcheck disable=SC2139 32 | alias lsd="ls -lF ${colorflag} | grep --color=never '^d'" 33 | 34 | # See: https://superuser.com/a/656746/280737 35 | alias ll='LC_ALL="C.UTF-8" ls -alF' 36 | 37 | # Always use color output for `ls` 38 | # shellcheck disable=SC2139 39 | alias ls="command ls ${colorflag}" 40 | export LS_COLORS='no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:' 41 | 42 | # Always enable colored `grep` output 43 | alias grep='grep --color=auto ' 44 | 45 | # Enable aliases to be sudo’ed 46 | alias sudo='sudo ' 47 | 48 | # Get week number 49 | alias week='date +%V' 50 | 51 | # Stopwatch 52 | alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date' 53 | 54 | # Canonical hex dump; some systems have this symlinked 55 | command -v hd > /dev/null || alias hd="hexdump -C" 56 | 57 | # vhosts 58 | alias hosts='sudo nano /etc/hosts' 59 | 60 | # copy working directory 61 | alias cwd='pwd | tr -d "\r\n" | xclip -selection clipboard' 62 | 63 | # copy file interactive 64 | alias cp='cp -i' 65 | 66 | # move file interactive 67 | alias mv='mv -i' 68 | 69 | # untar 70 | alias untar='tar xvf' 71 | 72 | # Zephir related 73 | alias untar='tar xvf' 74 | 75 | PATH=$PATH:./vendor/bin 76 | -------------------------------------------------------------------------------- /docker/8.0/config/extra.ini: -------------------------------------------------------------------------------- 1 | error_reporting = E_ALL 2 | display_errors = "On" 3 | display_startup_errors = "On" 4 | log_errors = "On" 5 | error_log = /srv/storage/logs/php_errors.log 6 | memory_limit = 512M 7 | apc.enable_cli = "On" 8 | session.save_path = "/tmp" 9 | -------------------------------------------------------------------------------- /docker/8.1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM composer:latest as composer 2 | FROM php:8.1-fpm 3 | 4 | # Set working directory 5 | WORKDIR /var/www 6 | 7 | LABEL vendor="Phalcon" \ 8 | maintainer="Phalcon Team " \ 9 | description="The PHP image to test the REST API example concepts" 10 | 11 | ENV PHALCON_VERSION="5.0.1" \ 12 | PHP_VERSION="8.1" 13 | 14 | # Update 15 | RUN apt update -y && \ 16 | apt install -y \ 17 | apt-utils \ 18 | gettext \ 19 | git \ 20 | libzip-dev \ 21 | nano \ 22 | sudo \ 23 | wget \ 24 | zip 25 | 26 | # PECL Packages 27 | RUN pecl install -o -f redis && \ 28 | pecl install phalcon-${PHALCON_VERSION} \ 29 | xdebug 30 | 31 | # Install PHP extensions 32 | RUN docker-php-ext-install \ 33 | gettext \ 34 | pdo_mysql \ 35 | zip 36 | 37 | # Install PHP extensions 38 | RUN docker-php-ext-enable \ 39 | opcache \ 40 | phalcon \ 41 | redis \ 42 | xdebug 43 | 44 | # Clear cache 45 | RUN apt-get clean && rm -rf /var/lib/apt/lists/* 46 | 47 | # Add user 48 | RUN groupadd -g 1000 phalcon 49 | RUN useradd -u 1000 -ms /bin/bash -g phalcon phalcon 50 | 51 | # Composer 52 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 53 | 54 | # Copy existing application directory contents 55 | COPY . /var/www 56 | 57 | # Bash script with helper aliases 58 | COPY ./config/.bashrc /root/.bashrc 59 | COPY ./config/.bashrc /home/phalcon/.bashrc 60 | 61 | # Copy existing application directory permissions 62 | COPY --chown=phalcon:phalcon . /var/www 63 | 64 | # Change current user to phalcon 65 | USER phalcon 66 | 67 | # Expose port 9000 and start php-fpm server 68 | EXPOSE 9000 69 | 70 | CMD ["php-fpm"] 71 | -------------------------------------------------------------------------------- /docker/8.1/config/.bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Easier navigation: .., ..., ...., ....., ~ and - 4 | alias ..="cd .." 5 | alias ...="cd ../.." 6 | alias ....="cd ../../.." 7 | alias .....="cd ../../../.." 8 | alias ~="cd ~" # `cd` is probably faster to type though 9 | alias -- -="cd -" 10 | 11 | # Shortcuts 12 | alias g="git" 13 | alias h="history" 14 | 15 | # Detect which `ls` flavor is in use 16 | if ls --color > /dev/null 2>&1; then # GNU `ls` 17 | colorflag="--color" 18 | else # OS X `ls` 19 | colorflag="-G" 20 | fi 21 | 22 | # List all files colorized in long format 23 | # shellcheck disable=SC2139 24 | alias l="ls -lF ${colorflag}" 25 | 26 | # List all files colorized in long format, including dot files 27 | # shellcheck disable=SC2139 28 | alias la="ls -laF ${colorflag}" 29 | 30 | # List only directories 31 | # shellcheck disable=SC2139 32 | alias lsd="ls -lF ${colorflag} | grep --color=never '^d'" 33 | 34 | # See: https://superuser.com/a/656746/280737 35 | alias ll='LC_ALL="C.UTF-8" ls -alF' 36 | 37 | # Always use color output for `ls` 38 | # shellcheck disable=SC2139 39 | alias ls="command ls ${colorflag}" 40 | export LS_COLORS='no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:' 41 | 42 | # Always enable colored `grep` output 43 | alias grep='grep --color=auto ' 44 | 45 | # Enable aliases to be sudo’ed 46 | alias sudo='sudo ' 47 | 48 | # Get week number 49 | alias week='date +%V' 50 | 51 | # Stopwatch 52 | alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date' 53 | 54 | # Canonical hex dump; some systems have this symlinked 55 | command -v hd > /dev/null || alias hd="hexdump -C" 56 | 57 | # vhosts 58 | alias hosts='sudo nano /etc/hosts' 59 | 60 | # copy working directory 61 | alias cwd='pwd | tr -d "\r\n" | xclip -selection clipboard' 62 | 63 | # copy file interactive 64 | alias cp='cp -i' 65 | 66 | # move file interactive 67 | alias mv='mv -i' 68 | 69 | # untar 70 | alias untar='tar xvf' 71 | 72 | # Zephir related 73 | alias untar='tar xvf' 74 | 75 | PATH=$PATH:./vendor/bin 76 | -------------------------------------------------------------------------------- /docker/8.1/config/extra.ini: -------------------------------------------------------------------------------- 1 | error_reporting = E_ALL 2 | display_errors = "On" 3 | display_startup_errors = "On" 4 | log_errors = "On" 5 | error_log = /srv/storage/logs/php_errors.log 6 | memory_limit = 512M 7 | apc.enable_cli = "On" 8 | session.save_path = "/tmp" 9 | -------------------------------------------------------------------------------- /library/Bootstrap/AbstractBootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Bootstrap; 15 | 16 | use Phalcon\Api\Http\Response; 17 | use Phalcon\Cli\Console; 18 | use Phalcon\Di\FactoryDefault; 19 | use Phalcon\Di\FactoryDefault\Cli as PhCli; 20 | use Phalcon\Di\ServiceProviderInterface; 21 | use Phalcon\Mvc\Micro; 22 | 23 | use function Phalcon\Api\Core\appPath; 24 | 25 | abstract class AbstractBootstrap 26 | { 27 | /** 28 | * @var Console|Micro|null 29 | */ 30 | protected Console|Micro|null $application = null; 31 | 32 | /** @var FactoryDefault|PhCli|null */ 33 | protected FactoryDefault|PhCli|null $container = null; 34 | 35 | /** @var array */ 36 | protected array $options = []; 37 | 38 | /** @var array */ 39 | protected array $providers = []; 40 | 41 | /** 42 | * Constructor 43 | */ 44 | public function __construct() 45 | { 46 | if (null === $this->container) { 47 | $this->container = new FactoryDefault(); 48 | } 49 | 50 | if ([] === $this->providers) { 51 | $this->providers = require appPath('api/config/providers.php'); 52 | } 53 | 54 | $this 55 | ->setupApplication() 56 | ->registerServices() 57 | ; 58 | } 59 | 60 | /** 61 | * @return Console|Micro|null 62 | */ 63 | public function getApplication(): Console|Micro|null 64 | { 65 | return $this->application; 66 | } 67 | 68 | /** 69 | * @return FactoryDefault|PhCli|null 70 | */ 71 | public function getContainer(): FactoryDefault|PhCli|null 72 | { 73 | return $this->container; 74 | } 75 | 76 | /** 77 | * @return Response 78 | */ 79 | public function getResponse(): Response 80 | { 81 | return $this->container->getShared('response'); 82 | } 83 | 84 | /** 85 | * @return mixed 86 | */ 87 | abstract public function run(); 88 | 89 | /** 90 | * Set up the application object in the container 91 | * 92 | * @return AbstractBootstrap 93 | */ 94 | protected function setupApplication(): AbstractBootstrap 95 | { 96 | $this->application = new Micro($this->container); 97 | $this->container->setShared('application', $this->application); 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * Registers available services 104 | * 105 | * @return void 106 | */ 107 | private function registerServices() 108 | { 109 | /** @var ServiceProviderInterface $provider */ 110 | foreach ($this->providers as $provider) { 111 | (new $provider())->register($this->container); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /library/Bootstrap/Api.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Bootstrap; 15 | 16 | use Phalcon\Mvc\Micro; 17 | 18 | /** 19 | * Class Api 20 | * 21 | * @property Micro $application 22 | */ 23 | class Api extends AbstractBootstrap 24 | { 25 | /** 26 | * Run the application 27 | * 28 | * @return mixed|void 29 | */ 30 | public function run() 31 | { 32 | $uri = $_SERVER['REQUEST_URI'] ?? '/'; 33 | 34 | return $this->application->handle($uri); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /library/Bootstrap/Cli.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Bootstrap; 15 | 16 | use Phalcon\Cli\Console; 17 | use Phalcon\Di\FactoryDefault\Cli as PhCli; 18 | 19 | use function Phalcon\Api\Core\appPath; 20 | 21 | /** 22 | * Class Cli 23 | * 24 | * @property Console $application 25 | */ 26 | class Cli extends AbstractBootstrap 27 | { 28 | /** 29 | * Constructor 30 | */ 31 | public function __construct() 32 | { 33 | $this->container = new PhCli(); 34 | $this->providers = require appPath('cli/config/providers.php'); 35 | 36 | $this->processArguments(); 37 | 38 | parent::__construct(); 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Run the application 45 | * 46 | * @return mixed 47 | */ 48 | public function run() 49 | { 50 | return $this->application->handle($this->options); 51 | } 52 | 53 | /** 54 | * Set up the application object in the container 55 | * 56 | * @return Cli 57 | */ 58 | protected function setupApplication(): Cli 59 | { 60 | $this->application = new Console($this->container); 61 | $this->container->setShared('application', $this->application); 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Parses arguments from the command line 68 | * 69 | * @return Cli 70 | */ 71 | private function processArguments(): Cli 72 | { 73 | $this->options = [ 74 | 'task' => 'Main', 75 | ]; 76 | 77 | $options = [ 78 | 'clear-cache' => 'ClearCache', 79 | 'help' => 'Main', 80 | ]; 81 | 82 | $arguments = getopt('', array_keys($options)); 83 | 84 | foreach ($options as $option => $task) { 85 | if (true === isset($arguments[$option])) { 86 | $this->options['task'] = $task; 87 | } 88 | } 89 | 90 | return $this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /library/Bootstrap/Tests.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Bootstrap; 15 | 16 | class Tests extends Api 17 | { 18 | /** 19 | * Run the application 20 | * 21 | * @return mixed 22 | */ 23 | public function run() 24 | { 25 | return $this->application; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/Constants/Flags.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Constants; 15 | 16 | class Flags 17 | { 18 | public const ACTIVE = 1; 19 | public const INACTIVE = 2; 20 | } 21 | -------------------------------------------------------------------------------- /library/Constants/Relationships.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Constants; 15 | 16 | class Relationships 17 | { 18 | public const COMPANIES = 'companies'; 19 | public const INDIVIDUAL_TYPES = 'individual-types'; 20 | public const INDIVIDUALS = 'individuals'; 21 | public const PRODUCT_TYPES = 'product-types'; 22 | public const PRODUCTS = 'products'; 23 | public const USERS = 'users'; 24 | } 25 | -------------------------------------------------------------------------------- /library/Core/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | use Dotenv\Dotenv; 15 | use Phalcon\Autoload\Loader; 16 | 17 | use function Phalcon\Api\Core\appPath; 18 | 19 | // Register the autoloader 20 | require __DIR__ . '/functions.php'; 21 | 22 | $loader = new Loader(); 23 | $namespaces = [ 24 | 'Phalcon\Api' => appPath('/library'), 25 | 'Phalcon\Api\Api\Controllers' => appPath('/api/controllers'), 26 | 'Phalcon\Api\Cli\Tasks' => appPath('/cli/tasks'), 27 | 'Phalcon\Api\Tests' => appPath('/tests'), 28 | ]; 29 | 30 | $loader->setNamespaces($namespaces); 31 | $loader->register(); 32 | 33 | /** 34 | * Composer Autoloader 35 | */ 36 | require appPath('/vendor/autoload.php'); 37 | 38 | // Load environment 39 | (Dotenv::createImmutable(appPath()))->load(); 40 | -------------------------------------------------------------------------------- /library/Core/config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | use Phalcon\Mvc\Model\MetaData\Memory; 15 | use Phalcon\Mvc\Model\MetaData\Redis; 16 | 17 | use function Phalcon\Api\Core\envValue; 18 | 19 | return [ 20 | 'app' => [ 21 | 'version' => envValue('VERSION', time()), 22 | 'timezone' => envValue('APP_TIMEZONE', 'UTC'), 23 | 'debug' => envValue('APP_DEBUG', false), 24 | 'env' => envValue('APP_ENV', 'development'), 25 | 'devMode' => 'development' === envValue('APP_ENV', 'development'), 26 | 'baseUri' => envValue('APP_BASE_URI'), 27 | 'supportEmail' => envValue('APP_SUPPORT_EMAIL'), 28 | 'time' => hrtime(true), 29 | ], 30 | 'cache' => [ 31 | 'adapter' => 'redis', 32 | 'options' => [ 33 | 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), 34 | 'port' => envValue('DATA_API_REDIS_PORT', 6379), 35 | 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), 36 | 'lifetime' => envValue('CACHE_LIFETIME', 86400), 37 | 'prefix' => 'data-', 38 | ], 39 | ], 40 | 'metadata' => [ 41 | 'dev' => [ 42 | 'adapter' => Memory::class, 43 | 'options' => [], 44 | ], 45 | 'prod' => [ 46 | 'adapter' => Redis::class, 47 | 'options' => [ 48 | 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), 49 | 'port' => envValue('DATA_API_REDIS_PORT', 6379), 50 | 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), 51 | 'lifetime' => envValue('CACHE_LIFETIME', 86400), 52 | 'prefix' => 'metadata-', 53 | ], 54 | ], 55 | ], 56 | ]; 57 | -------------------------------------------------------------------------------- /library/Core/functions.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Core; 15 | 16 | use function function_exists; 17 | 18 | if (true !== function_exists('Phalcon\Api\Core\appPath')) { 19 | /** 20 | * Get the application path. 21 | * 22 | * @param string $path 23 | * 24 | * @return string 25 | */ 26 | function appPath(string $path = ''): string 27 | { 28 | return dirname(dirname(__DIR__)) . ($path ? DIRECTORY_SEPARATOR . $path : $path); 29 | } 30 | } 31 | 32 | if (true !== function_exists('Phalcon\Api\Core\envValue')) { 33 | /** 34 | * Gets a variable from the environment, returns it properly formatted or the 35 | * default if it does not exist 36 | * 37 | * @param string $variable 38 | * @param mixed|null $default 39 | * 40 | * @return mixed 41 | */ 42 | function envValue(string $variable, mixed $default = null): mixed 43 | { 44 | $value = $_ENV[$variable] ?? $default; 45 | $values = [ 46 | 'false' => false, 47 | 'true' => true, 48 | 'null' => null, 49 | ]; 50 | 51 | return $values[$value] ?? $value; 52 | } 53 | } 54 | 55 | if (true !== function_exists('Phalcon\Api\Core\appUrl')) { 56 | /** 57 | * Constructs a URL for links with resource and id 58 | * 59 | * @param string $resource 60 | * @param int $recordId 61 | * 62 | * @return array|false|mixed|string 63 | */ 64 | function appUrl(string $resource, int $recordId) 65 | { 66 | return sprintf( 67 | '%s/%s/%s', 68 | envValue('APP_URL'), 69 | $resource, 70 | $recordId 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /library/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api; 15 | 16 | use Phalcon\Config\Config; 17 | use Phalcon\Logger\Exception; 18 | use Phalcon\Logger\Logger; 19 | 20 | use function memory_get_usage; 21 | use function number_format; 22 | use function sprintf; 23 | 24 | /** 25 | * Class ErrorHandler 26 | */ 27 | class ErrorHandler 28 | { 29 | /** @var Config */ 30 | private $config; 31 | 32 | /** @var Logger */ 33 | private $logger; 34 | 35 | /** 36 | * ErrorHandler constructor. 37 | * 38 | * @param Logger $logger 39 | * @param Config $config 40 | */ 41 | public function __construct(Logger $logger, Config $config) 42 | { 43 | $this->config = $config; 44 | $this->logger = $logger; 45 | } 46 | 47 | /** 48 | * Handles errors by logging them 49 | * 50 | * @param int $number 51 | * @param string $message 52 | * @param string $file 53 | * @param int $line 54 | * 55 | * @return void 56 | * @throws Exception 57 | */ 58 | public function handle( 59 | int $number, 60 | string $message, 61 | string $file = '', 62 | int $line = 0 63 | ): void { 64 | $this 65 | ->logger 66 | ->error( 67 | sprintf( 68 | '[#:%s]-[L: %s] : %s (%s)', 69 | $number, 70 | $line, 71 | $message, 72 | $file 73 | ) 74 | ) 75 | ; 76 | } 77 | 78 | /** 79 | * Application shutdown - logs metrics in devMode 80 | * 81 | * @return void 82 | * @throws Exception 83 | */ 84 | public function shutdown(): void 85 | { 86 | if (true === $this->config->path('app.devMode')) { 87 | $memory = number_format(memory_get_usage() / 1000000, 2); 88 | $execution = number_format( 89 | (hrtime(true) - $this->config->path('app.time')) / 1000000, 90 | 4 91 | ); 92 | 93 | $this 94 | ->logger 95 | ->info( 96 | sprintf( 97 | 'Shutdown completed [%s]ms - [%s]MB', 98 | $execution, 99 | $memory 100 | ) 101 | ) 102 | ; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /library/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Exception; 15 | 16 | class Exception extends \Exception 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /library/Exception/HttpException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Exception; 15 | 16 | class HttpException extends Exception 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /library/Exception/ModelException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Exception; 15 | 16 | class ModelException extends Exception 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /library/Http/Request.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Http; 15 | 16 | use Phalcon\Http\Request as PhRequest; 17 | 18 | use function str_replace; 19 | 20 | class Request extends PhRequest 21 | { 22 | /** 23 | * @return string 24 | */ 25 | public function getBearerTokenFromHeader(): string 26 | { 27 | return str_replace('Bearer ', '', $this->getHeader('Authorization')); 28 | } 29 | 30 | /** 31 | * @return bool 32 | */ 33 | public function isEmptyBearerToken(): bool 34 | { 35 | return true === empty($this->getBearerTokenFromHeader()); 36 | } 37 | 38 | /** 39 | * @return bool 40 | */ 41 | public function isLoginPage(): bool 42 | { 43 | return ('/login' === $this->getURI()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/Middleware/AuthenticationMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Http\Request; 17 | use Phalcon\Api\Http\Response; 18 | use Phalcon\Api\Traits\QueryTrait; 19 | use Phalcon\Api\Traits\ResponseTrait; 20 | use Phalcon\Mvc\Micro; 21 | use Phalcon\Mvc\Micro\MiddlewareInterface; 22 | 23 | /** 24 | * Class AuthenticationMiddleware 25 | */ 26 | class AuthenticationMiddleware implements MiddlewareInterface 27 | { 28 | use ResponseTrait; 29 | use QueryTrait; 30 | 31 | /** 32 | * Call me 33 | * 34 | * @param Micro $api 35 | * 36 | * @return bool 37 | */ 38 | public function call(Micro $api): bool 39 | { 40 | /** @var Request $request */ 41 | $request = $api->getService('request'); 42 | /** @var Response $response */ 43 | $response = $api->getService('response'); 44 | 45 | if ( 46 | true !== $request->isLoginPage() && 47 | true === $request->isEmptyBearerToken() 48 | ) { 49 | $this->halt( 50 | $api, 51 | $response::OK, 52 | 'Invalid Token' 53 | ); 54 | 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/Middleware/NotFoundMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Http\Response; 17 | use Phalcon\Api\Traits\ResponseTrait; 18 | use Phalcon\Di\Injectable; 19 | use Phalcon\Mvc\Micro; 20 | use Phalcon\Mvc\Micro\MiddlewareInterface; 21 | 22 | /** 23 | * Class NotFoundMiddleware 24 | * 25 | * @property Micro $application 26 | * @property Response $response 27 | */ 28 | class NotFoundMiddleware extends Injectable implements MiddlewareInterface 29 | { 30 | use ResponseTrait; 31 | 32 | /** 33 | * Checks if the resource was found 34 | * 35 | * @return bool 36 | */ 37 | public function beforeNotFound(): bool 38 | { 39 | $this->halt( 40 | $this->application, 41 | $this->response::NOT_FOUND, 42 | $this->response->getHttpCodeDescription($this->response::NOT_FOUND) 43 | ); 44 | 45 | return false; 46 | } 47 | 48 | /** 49 | * Call me 50 | * 51 | * @param Micro $api 52 | * 53 | * @return bool 54 | */ 55 | public function call(Micro $api): bool 56 | { 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /library/Middleware/ResponseMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Http\Response; 17 | use Phalcon\Api\Traits\ResponseTrait; 18 | use Phalcon\Mvc\Micro; 19 | use Phalcon\Mvc\Micro\MiddlewareInterface; 20 | 21 | class ResponseMiddleware implements MiddlewareInterface 22 | { 23 | use ResponseTrait; 24 | 25 | /** 26 | * Call me 27 | * 28 | * @param Micro $api 29 | * 30 | * @return bool 31 | */ 32 | public function call(Micro $api): bool 33 | { 34 | /** @var Response $response */ 35 | $response = $api->getService('response'); 36 | $response->send(); 37 | 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /library/Middleware/TokenBase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Http\Request; 17 | use Phalcon\Api\Traits\QueryTrait; 18 | use Phalcon\Api\Traits\ResponseTrait; 19 | use Phalcon\Api\Traits\TokenTrait; 20 | use Phalcon\Mvc\Micro\MiddlewareInterface; 21 | 22 | /** 23 | * Class AuthenticationMiddleware 24 | */ 25 | abstract class TokenBase implements MiddlewareInterface 26 | { 27 | use ResponseTrait; 28 | use TokenTrait; 29 | use QueryTrait; 30 | 31 | /** 32 | * @param Request $request 33 | * 34 | * @return bool 35 | */ 36 | protected function isValidCheck(Request $request): bool 37 | { 38 | return ( 39 | true !== $request->isLoginPage() && 40 | true !== $request->isEmptyBearerToken() 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /library/Middleware/TokenUserMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Http\Request; 17 | use Phalcon\Api\Http\Response; 18 | use Phalcon\Api\Models\Users; 19 | use Phalcon\Cache\Cache; 20 | use Phalcon\Config\Config; 21 | use Phalcon\Mvc\Micro; 22 | 23 | /** 24 | * Class TokenUserMiddleware 25 | */ 26 | class TokenUserMiddleware extends TokenBase 27 | { 28 | /** 29 | * @param Micro $api 30 | * 31 | * @return bool 32 | */ 33 | public function call(Micro $api): bool 34 | { 35 | /** @var Cache $cache */ 36 | $cache = $api->getService('cache'); 37 | /** @var Config $config */ 38 | $config = $api->getService('config'); 39 | /** @var Request $request */ 40 | $request = $api->getService('request'); 41 | /** @var Response $response */ 42 | $response = $api->getService('response'); 43 | if (true === $this->isValidCheck($request)) { 44 | /** 45 | * This is where we will find if the user exists based on 46 | * the token passed using Bearer Authentication 47 | */ 48 | $token = $this->getToken($request->getBearerTokenFromHeader()); 49 | 50 | /** @var Users|null $user */ 51 | $user = $this->getUserByToken($config, $cache, $token); 52 | if (null === $user) { 53 | $this->halt( 54 | $api, 55 | $response::OK, 56 | 'Invalid token (user)' 57 | ); 58 | 59 | return false; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /library/Middleware/TokenValidationMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Exception\ModelException; 17 | use Phalcon\Api\Http\Request; 18 | use Phalcon\Api\Http\Response; 19 | use Phalcon\Api\Models\Users; 20 | use Phalcon\Cache\Cache; 21 | use Phalcon\Config\Config; 22 | use Phalcon\Mvc\Micro; 23 | use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; 24 | use Phalcon\Encryption\Security\JWT\Signer\Hmac; 25 | use Phalcon\Encryption\Security\JWT\Validator; 26 | 27 | use function implode; 28 | 29 | /** 30 | * Class TokenValidationMiddleware 31 | */ 32 | class TokenValidationMiddleware extends TokenBase 33 | { 34 | /** 35 | * @param Micro $api 36 | * 37 | * @return bool 38 | * @throws ModelException 39 | */ 40 | public function call(Micro $api): bool 41 | { 42 | /** @var Cache $cache */ 43 | $cache = $api->getService('cache'); 44 | /** @var Config $config */ 45 | $config = $api->getService('config'); 46 | /** @var Request $request */ 47 | $request = $api->getService('request'); 48 | /** @var Response $response */ 49 | $response = $api->getService('response'); 50 | if (true === $this->isValidCheck($request)) { 51 | /** 52 | * This is where we will validate the token that was sent to us 53 | * using Bearer Authentication 54 | * 55 | * Find the user attached to this token 56 | */ 57 | $token = $this->getToken($request->getBearerTokenFromHeader()); 58 | 59 | /** @var Users $user */ 60 | $user = $this->getUserByToken($config, $cache, $token); 61 | $errors = $token->validate($user->getValidationData()); 62 | 63 | if (true !== empty($errors)) { 64 | $this->halt( 65 | $api, 66 | $response::OK, 67 | 'Invalid Token [' . implode('; ', $errors) . ']' 68 | ); 69 | 70 | return false; 71 | } 72 | } 73 | 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /library/Middleware/TokenVerificationMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Middleware; 15 | 16 | use Phalcon\Api\Exception\ModelException; 17 | use Phalcon\Api\Http\Request; 18 | use Phalcon\Api\Http\Response; 19 | use Phalcon\Api\Models\Users; 20 | use Phalcon\Cache\Cache; 21 | use Phalcon\Config\Config; 22 | use Phalcon\Mvc\Micro; 23 | use Phalcon\Encryption\Security\JWT\Signer\Hmac; 24 | use Phalcon\Encryption\Security\JWT\Validator; 25 | 26 | /** 27 | * Class AuthenticationMiddleware 28 | */ 29 | class TokenVerificationMiddleware extends TokenBase 30 | { 31 | /** 32 | * @param Micro $api 33 | * 34 | * @return bool 35 | * @throws ModelException 36 | */ 37 | public function call(Micro $api): bool 38 | { 39 | /** @var Cache $cache */ 40 | $cache = $api->getService('cache'); 41 | /** @var Config $config */ 42 | $config = $api->getService('config'); 43 | /** @var Request $request */ 44 | $request = $api->getService('request'); 45 | /** @var Response $response */ 46 | $response = $api->getService('response'); 47 | if (true === $this->isValidCheck($request)) { 48 | /** 49 | * This is where we will validate the token that was sent to us 50 | * using Bearer Authentication 51 | * 52 | * Find the user attached to this token 53 | */ 54 | $token = $this->getToken($request->getBearerTokenFromHeader()); 55 | $signer = new Hmac(); 56 | 57 | /** @var Users $user */ 58 | $user = $this->getUserByToken($config, $cache, $token); 59 | if (false === $token->verify($signer, $user->get('tokenPassword'))) { 60 | $this->halt( 61 | $api, 62 | $response::OK, 63 | 'Invalid Token (verification)' 64 | ); 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /library/Models/Companies.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Filter\Filter; 19 | use Phalcon\Filter\Validation; 20 | use Phalcon\Filter\Validation\Validator\Uniqueness; 21 | 22 | /** 23 | * Class Companies 24 | */ 25 | class Companies extends AbstractModel 26 | { 27 | /** 28 | * Initialize relationships and model properties 29 | * 30 | * @return void 31 | */ 32 | public function initialize(): void 33 | { 34 | $this->setSource('co_companies'); 35 | 36 | $this->hasMany( 37 | 'id', 38 | Individuals::class, 39 | 'companyId', 40 | [ 41 | 'alias' => Relationships::INDIVIDUALS, 42 | 'reusable' => true, 43 | ] 44 | ); 45 | 46 | $this->hasManyToMany( 47 | 'id', 48 | CompaniesXProducts::class, 49 | 'companyId', 50 | 'productId', 51 | Products::class, 52 | 'id', 53 | [ 54 | 'alias' => Relationships::PRODUCTS, 55 | 'reusable' => true, 56 | ] 57 | ); 58 | 59 | parent::initialize(); 60 | } 61 | 62 | /** 63 | * Model filters 64 | * 65 | * @return array 66 | */ 67 | public function getModelFilters(): array 68 | { 69 | return [ 70 | 'id' => Filter::FILTER_ABSINT, 71 | 'name' => Filter::FILTER_STRING, 72 | 'address' => Filter::FILTER_STRING, 73 | 'city' => Filter::FILTER_STRING, 74 | 'phone' => Filter::FILTER_STRING, 75 | ]; 76 | } 77 | 78 | /** 79 | * Validates the company name 80 | * 81 | * @return bool 82 | */ 83 | public function validation() 84 | { 85 | $validator = new Validation(); 86 | $validator->add( 87 | 'name', 88 | new Uniqueness( 89 | [ 90 | 'message' => 'The company name already exists in the database', 91 | ] 92 | ) 93 | ); 94 | 95 | return $this->validate($validator); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /library/Models/CompaniesXProducts.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Filter\Filter; 19 | 20 | /** 21 | * Class CompaniesXProducts 22 | */ 23 | class CompaniesXProducts extends AbstractModel 24 | { 25 | /** 26 | * Initialize relationships and model properties 27 | * 28 | * @return void 29 | */ 30 | public function initialize(): void 31 | { 32 | $this->setSource('co_companies_x_products'); 33 | 34 | $this->belongsTo( 35 | 'companyId', 36 | Companies::class, 37 | 'id', 38 | [ 39 | 'alias' => Relationships::COMPANIES, 40 | 'reusable' => true, 41 | ] 42 | ); 43 | 44 | $this->belongsTo( 45 | 'productId', 46 | Products::class, 47 | 'id', 48 | [ 49 | 'alias' => Relationships::PRODUCTS, 50 | 'reusable' => true, 51 | ] 52 | ); 53 | 54 | parent::initialize(); 55 | } 56 | 57 | /** 58 | * Model filters 59 | * 60 | * @return array 61 | */ 62 | public function getModelFilters(): array 63 | { 64 | return [ 65 | 'companyId' => Filter::FILTER_ABSINT, 66 | 'productId' => Filter::FILTER_ABSINT, 67 | ]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /library/Models/IndividualTypes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Filter\Filter; 19 | 20 | /** 21 | * Class IndividualTypes 22 | */ 23 | class IndividualTypes extends AbstractModel 24 | { 25 | /** 26 | * Initialize relationships and model properties 27 | * 28 | * @return void 29 | */ 30 | public function initialize(): void 31 | { 32 | $this->setSource('co_individual_types'); 33 | 34 | $this->hasMany( 35 | 'id', 36 | Individuals::class, 37 | 'typeId', 38 | [ 39 | 'alias' => Relationships::INDIVIDUALS, 40 | 'reusable' => true, 41 | ] 42 | ); 43 | 44 | parent::initialize(); 45 | } 46 | 47 | /** 48 | * Model filters 49 | * 50 | * @return array 51 | */ 52 | public function getModelFilters(): array 53 | { 54 | return [ 55 | 'id' => Filter::FILTER_ABSINT, 56 | 'name' => Filter::FILTER_STRING, 57 | 'description' => Filter::FILTER_STRING, 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/Models/Individuals.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Filter\Filter; 19 | 20 | /** 21 | * Class Individuals 22 | */ 23 | class Individuals extends AbstractModel 24 | { 25 | /** 26 | * Initialize relationships and model properties 27 | * 28 | * @return void 29 | */ 30 | public function initialize(): void 31 | { 32 | $this->setSource('co_individuals'); 33 | 34 | $this->belongsTo( 35 | 'companyId', 36 | Companies::class, 37 | 'id', 38 | [ 39 | 'alias' => Relationships::COMPANIES, 40 | 'reusable' => true, 41 | ] 42 | ); 43 | 44 | $this->hasOne( 45 | 'typeId', 46 | IndividualTypes::class, 47 | 'id', 48 | [ 49 | 'alias' => Relationships::INDIVIDUAL_TYPES, 50 | 'reusable' => true, 51 | ] 52 | ); 53 | 54 | parent::initialize(); 55 | } 56 | 57 | /** 58 | * Model filters 59 | * 60 | * @return array 61 | */ 62 | public function getModelFilters(): array 63 | { 64 | return [ 65 | 'id' => Filter::FILTER_ABSINT, 66 | 'companyId' => Filter::FILTER_ABSINT, 67 | 'typeId' => Filter::FILTER_ABSINT, 68 | 'prefix' => Filter::FILTER_STRING, 69 | 'first' => Filter::FILTER_STRING, 70 | 'middle' => Filter::FILTER_STRING, 71 | 'last' => Filter::FILTER_STRING, 72 | 'suffix' => Filter::FILTER_STRING, 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /library/Models/ProductTypes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Filter\Filter; 19 | 20 | /** 21 | * Class ProductTypes 22 | */ 23 | class ProductTypes extends AbstractModel 24 | { 25 | /** 26 | * Initialize relationships and model properties 27 | * 28 | * @return void 29 | */ 30 | public function initialize(): void 31 | { 32 | $this->setSource('co_product_types'); 33 | 34 | $this->hasMany( 35 | 'id', 36 | Products::class, 37 | 'typeId', 38 | [ 39 | 'alias' => Relationships::PRODUCTS, 40 | 'reusable' => true, 41 | ] 42 | ); 43 | 44 | parent::initialize(); 45 | } 46 | 47 | /** 48 | * Model filters 49 | * 50 | * @return array 51 | */ 52 | public function getModelFilters(): array 53 | { 54 | return [ 55 | 'id' => Filter::FILTER_ABSINT, 56 | 'name' => Filter::FILTER_STRING, 57 | 'description' => Filter::FILTER_STRING, 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/Models/Products.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Constants\Relationships; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Filter\Filter; 19 | 20 | /** 21 | * Class Products 22 | */ 23 | class Products extends AbstractModel 24 | { 25 | /** 26 | * Initialize relationships and model properties 27 | * 28 | * @return void 29 | */ 30 | public function initialize(): void 31 | { 32 | $this->setSource('co_products'); 33 | 34 | $this->hasManyToMany( 35 | 'id', 36 | CompaniesXProducts::class, 37 | 'productId', 38 | 'companyId', 39 | Companies::class, 40 | 'id', 41 | [ 42 | 'alias' => Relationships::COMPANIES, 43 | 'reusable' => true, 44 | ] 45 | ); 46 | 47 | $this->belongsTo( 48 | 'typeId', 49 | ProductTypes::class, 50 | 'id', 51 | [ 52 | 'alias' => Relationships::PRODUCT_TYPES, 53 | 'reusable' => true, 54 | ] 55 | ); 56 | 57 | parent::initialize(); 58 | } 59 | 60 | /** 61 | * Model filters 62 | * 63 | * @return array 64 | */ 65 | public function getModelFilters(): array 66 | { 67 | return [ 68 | 'id' => Filter::FILTER_ABSINT, 69 | 'typeId' => Filter::FILTER_ABSINT, 70 | 'name' => Filter::FILTER_STRING, 71 | 'description' => Filter::FILTER_STRING, 72 | 'quantity' => Filter::FILTER_ABSINT, 73 | 'price' => Filter::FILTER_FLOAT, 74 | ]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /library/Models/Users.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Models; 15 | 16 | use Phalcon\Api\Exception\ModelException; 17 | use Phalcon\Api\Mvc\Model\AbstractModel; 18 | use Phalcon\Api\Traits\TokenTrait; 19 | use Phalcon\Filter\Filter; 20 | use Phalcon\Encryption\Security\JWT\Builder; 21 | use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; 22 | use Phalcon\Encryption\Security\JWT\Signer\Hmac; 23 | use Phalcon\Encryption\Security\JWT\Token\Token; 24 | use Phalcon\Encryption\Security\JWT\Validator; 25 | 26 | /** 27 | * Class Users 28 | */ 29 | class Users extends AbstractModel 30 | { 31 | use TokenTrait; 32 | 33 | /** 34 | * Returns the source table from the database 35 | * 36 | * @return void 37 | */ 38 | public function initialize(): void 39 | { 40 | $this->setSource('co_users'); 41 | } 42 | 43 | /** 44 | * Model filters 45 | * 46 | * @return array 47 | */ 48 | public function getModelFilters(): array 49 | { 50 | return [ 51 | 'id' => Filter::FILTER_ABSINT, 52 | 'status' => Filter::FILTER_ABSINT, 53 | 'username' => Filter::FILTER_STRING, 54 | 'password' => Filter::FILTER_STRING, 55 | 'issuer' => Filter::FILTER_STRING, 56 | 'tokenPassword' => Filter::FILTER_STRING, 57 | 'tokenId' => Filter::FILTER_STRING, 58 | ]; 59 | } 60 | 61 | /** 62 | * Returns the string token 63 | * 64 | * @return string 65 | * @throws ModelException 66 | */ 67 | public function getToken(): string 68 | { 69 | $token = $this->getBuilderToken(); 70 | 71 | return $token->getToken(); 72 | } 73 | 74 | /** 75 | * Returns the Validator object for this record (JWT) 76 | * 77 | * @return Validator 78 | * @throws ModelException 79 | */ 80 | public function getValidationData(): Validator 81 | { 82 | $token = $this->getBuilderToken(); 83 | 84 | return new Validator($token, 10); 85 | } 86 | 87 | /** 88 | * @return Builder 89 | * @throws ModelException 90 | * @throws ValidatorException 91 | */ 92 | private function getBuilderToken(): Token 93 | { 94 | $signer = new Hmac(); 95 | $builder = new Builder($signer); 96 | 97 | return $builder 98 | ->setIssuer($this->get('issuer')) 99 | ->setAudience($this->getTokenAudience()) 100 | ->setId($this->get('tokenId')) 101 | ->setIssuedAt($this->getTokenTimeIssuedAt()) 102 | ->setNotBefore($this->getTokenTimeNotBefore()) 103 | ->setExpirationTime($this->getTokenTimeExpiration()) 104 | ->setPassphrase($this->get('tokenPassword')) 105 | ->getToken() 106 | ; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /library/Providers/CacheDataProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Cache\AdapterFactory; 17 | use Phalcon\Cache\Cache; 18 | use Phalcon\Config\Config; 19 | use Phalcon\Di\DiInterface; 20 | use Phalcon\Di\ServiceProviderInterface; 21 | use Phalcon\Storage\SerializerFactory; 22 | 23 | class CacheDataProvider implements ServiceProviderInterface 24 | { 25 | /** 26 | * @param DiInterface $container 27 | */ 28 | public function register(DiInterface $container): void 29 | { 30 | /** @var Config $config */ 31 | $config = $container->getShared('config'); 32 | 33 | $container->setShared( 34 | 'cache', 35 | function () use ($config) { 36 | $cache = $config->get('cache') 37 | ->toArray() 38 | ; 39 | $adapter = $cache['adapter']; 40 | $options = $cache['options'] ?? []; 41 | 42 | $serializerFactory = new SerializerFactory(); 43 | $adapterFactory = new AdapterFactory($serializerFactory); 44 | $adapter = $adapterFactory->newInstance($adapter, $options); 45 | 46 | return new Cache($adapter); 47 | } 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/Providers/CliDispatcherProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Cli\Dispatcher; 17 | use Phalcon\Di\DiInterface; 18 | use Phalcon\Di\ServiceProviderInterface; 19 | 20 | class CliDispatcherProvider implements ServiceProviderInterface 21 | { 22 | /** 23 | * @param DiInterface $container 24 | */ 25 | public function register(DiInterface $container): void 26 | { 27 | $container->setShared( 28 | 'dispatcher', 29 | function () { 30 | $dispatcher = new Dispatcher(); 31 | $dispatcher->setDefaultNamespace('Phalcon\Api\Cli\Tasks'); 32 | 33 | return $dispatcher; 34 | } 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /library/Providers/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Config\Config; 17 | use Phalcon\Di\DiInterface; 18 | use Phalcon\Di\ServiceProviderInterface; 19 | 20 | use function Phalcon\Api\Core\appPath; 21 | 22 | class ConfigProvider implements ServiceProviderInterface 23 | { 24 | /** 25 | * @param DiInterface $container 26 | */ 27 | public function register(DiInterface $container): void 28 | { 29 | $container->setShared( 30 | 'config', 31 | function () { 32 | $data = require appPath('library/Core/config.php'); 33 | 34 | return new Config($data); 35 | } 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /library/Providers/DatabaseProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Db\Adapter\Pdo\Mysql; 17 | use Phalcon\Di\DiInterface; 18 | use Phalcon\Di\ServiceProviderInterface; 19 | 20 | use function Phalcon\Api\Core\envValue; 21 | 22 | class DatabaseProvider implements ServiceProviderInterface 23 | { 24 | /** 25 | * @param DiInterface $container 26 | */ 27 | public function register(DiInterface $container): void 28 | { 29 | $container->setShared( 30 | 'db', 31 | function () { 32 | $options = [ 33 | 'host' => envValue('DATA_API_MYSQL_HOST', 'localhost'), 34 | 'username' => envValue('DATA_API_MYSQL_USER', 'phalcon'), 35 | 'password' => envValue('DATA_API_MYSQL_PASS', ''), 36 | 'dbname' => envValue('DATA_API_MYSQL_NAME', 'phalcon_api'), 37 | ]; 38 | 39 | $connection = new Mysql($options); 40 | // Set everything to UTF8 41 | $connection->execute('SET NAMES utf8mb4', []); 42 | 43 | return $connection; 44 | } 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/Providers/ErrorHandlerProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Api\ErrorHandler; 17 | use Phalcon\Config\Config; 18 | use Phalcon\Di\DiInterface; 19 | use Phalcon\Di\ServiceProviderInterface; 20 | use Phalcon\Logger\Logger; 21 | 22 | use function date_default_timezone_set; 23 | use function error_reporting; 24 | use function ini_set; 25 | use function register_shutdown_function; 26 | use function set_error_handler; 27 | 28 | use const E_ALL; 29 | 30 | class ErrorHandlerProvider implements ServiceProviderInterface 31 | { 32 | /** 33 | * {@inheritdoc} 34 | * 35 | * @param DiInterface $container 36 | */ 37 | public function register(DiInterface $container): void 38 | { 39 | /** @var Logger $logger */ 40 | $logger = $container->getShared('logger'); 41 | /** @var Config $registry */ 42 | $config = $container->getShared('config'); 43 | 44 | date_default_timezone_set($config->path('app.timezone')); 45 | ini_set('display_errors', 'Off'); 46 | error_reporting(E_ALL); 47 | 48 | $handler = new ErrorHandler($logger, $config); 49 | set_error_handler([$handler, 'handle']); 50 | register_shutdown_function([$handler, 'shutdown']); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/Providers/LoggerProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Di\DiInterface; 17 | use Phalcon\Di\ServiceProviderInterface; 18 | use Phalcon\Logger\Adapter\Stream; 19 | use Phalcon\Logger\Logger; 20 | 21 | use function Phalcon\Api\Core\appPath; 22 | use function Phalcon\Api\Core\envValue; 23 | 24 | class LoggerProvider implements ServiceProviderInterface 25 | { 26 | /** 27 | * Registers the logger component 28 | * 29 | * @param DiInterface $container 30 | */ 31 | public function register(DiInterface $container): void 32 | { 33 | $container->setShared( 34 | 'logger', 35 | function () { 36 | /** @var string $logName */ 37 | $logName = envValue('LOGGER_DEFAULT_FILENAME', 'api.log'); 38 | /** @var string $logPath */ 39 | $logPath = envValue('LOGGER_DEFAULT_PATH', 'storage/logs'); 40 | $logFile = appPath($logPath) . '/' . $logName . '.log'; 41 | $adapter = new Stream($logFile); 42 | $logger = new Logger( 43 | $logName, 44 | [ 45 | 'main' => $adapter, 46 | ] 47 | ); 48 | 49 | return $logger; 50 | } 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/Providers/ModelsMetadataProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Cache\AdapterFactory; 17 | use Phalcon\Config\Config; 18 | use Phalcon\Di\DiInterface; 19 | use Phalcon\Di\ServiceProviderInterface; 20 | use Phalcon\Mvc\Model\MetaData\Memory; 21 | use Phalcon\Mvc\Model\MetaData\Redis; 22 | use Phalcon\Storage\SerializerFactory; 23 | 24 | class ModelsMetadataProvider implements ServiceProviderInterface 25 | { 26 | /** 27 | * @param DiInterface $container 28 | */ 29 | public function register(DiInterface $container): void 30 | { 31 | /** @var Config $config */ 32 | $config = $container->getShared('config'); 33 | 34 | $container->setShared( 35 | 'modelsMetadata', 36 | function () use ($config) { 37 | $metadata = $config->get('metadata'); 38 | $devMode = $config->path('app.devMode'); 39 | $key = (true === $devMode) ? 'dev' : 'prod'; 40 | $options = $metadata->get($key, []) 41 | ->toArray() 42 | ; 43 | $adapter = $options['adapter'] ?? Redis::class; 44 | 45 | if ($adapter === Memory::class) { 46 | return new $adapter($options); 47 | } else { 48 | $serializer = new SerializerFactory(); 49 | $adapterFactory = new AdapterFactory($serializer); 50 | 51 | return new $adapter($adapterFactory, $options); 52 | } 53 | } 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/Providers/RequestProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Api\Http\Request; 17 | use Phalcon\Di\DiInterface; 18 | use Phalcon\Di\ServiceProviderInterface; 19 | 20 | class RequestProvider implements ServiceProviderInterface 21 | { 22 | /** 23 | * @param DiInterface $container 24 | */ 25 | public function register(DiInterface $container): void 26 | { 27 | $container->setShared('request', new Request()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /library/Providers/ResponseProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Providers; 15 | 16 | use Phalcon\Api\Http\Response; 17 | use Phalcon\Di\DiInterface; 18 | use Phalcon\Di\ServiceProviderInterface; 19 | 20 | class ResponseProvider implements ServiceProviderInterface 21 | { 22 | /** 23 | * @param DiInterface $container 24 | */ 25 | public function register(DiInterface $container): void 26 | { 27 | $container->setShared( 28 | 'response', 29 | function () { 30 | $response = new Response(); 31 | 32 | /** 33 | * Assume success. We will work with the edge cases in the code 34 | */ 35 | $response 36 | ->setStatusCode(200) 37 | ->setContentType('application/vnd.api+json', 'UTF-8') 38 | ; 39 | 40 | return $response; 41 | } 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/Traits/FractalTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Traits; 15 | 16 | use League\Fractal\Manager; 17 | use League\Fractal\Serializer\JsonApiSerializer; 18 | 19 | use function Phalcon\Api\Core\envValue; 20 | use function sprintf; 21 | use function ucfirst; 22 | 23 | /** 24 | * Trait FractalTrait 25 | */ 26 | trait FractalTrait 27 | { 28 | /** 29 | * Format results based on a transformer 30 | * 31 | * @param string $method 32 | * @param mixed $results 33 | * @param string $transformer 34 | * @param string $resource 35 | * @param array $relationships 36 | * @param array $fields 37 | * 38 | * @return array 39 | */ 40 | protected function format( 41 | string $method, 42 | $results, 43 | string $transformer, 44 | string $resource, 45 | array $relationships = [], 46 | array $fields = [] 47 | ): array { 48 | $url = envValue('APP_URL', 'http://localhost'); 49 | $manager = new Manager(); 50 | $manager->setSerializer(new JsonApiSerializer($url)); 51 | 52 | /** 53 | * Process relationships 54 | */ 55 | if (count($relationships) > 0) { 56 | $manager->parseIncludes($relationships); 57 | } 58 | 59 | $class = sprintf('League\Fractal\Resource\%s', ucfirst($method)); 60 | $resource = new $class($results, new $transformer($fields, $resource), $resource); 61 | $results = $manager->createData($resource) 62 | ->toArray() 63 | ; 64 | 65 | return $results; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /library/Traits/ResponseTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Traits; 15 | 16 | use Phalcon\Api\Http\Response; 17 | use Phalcon\Mvc\Micro; 18 | 19 | /** 20 | * Trait ResponseTrait 21 | */ 22 | trait ResponseTrait 23 | { 24 | /** 25 | * Halt execution after setting the message in the response 26 | * 27 | * @param Micro $api 28 | * @param int $status 29 | * @param string $message 30 | * 31 | * @return void 32 | */ 33 | protected function halt(Micro $api, int $status, string $message): void 34 | { 35 | /** @var Response $response */ 36 | $response = $api->getService('response'); 37 | $response 38 | ->setPayloadError($message) 39 | ->setStatusCode($status) 40 | ->send() 41 | ; 42 | 43 | $api->stop(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/Traits/TokenTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Traits; 15 | 16 | use Phalcon\Encryption\Security\JWT\Token\Parser; 17 | use Phalcon\Encryption\Security\JWT\Token\Token; 18 | 19 | use function Phalcon\Api\Core\envValue; 20 | use function time; 21 | 22 | /** 23 | * Trait TokenTrait 24 | */ 25 | trait TokenTrait 26 | { 27 | /** 28 | * Returns the JWT token object 29 | * 30 | * @param string $token 31 | * 32 | * @return Token 33 | */ 34 | protected function getToken(string $token): Token 35 | { 36 | return (new Parser())->parse($token); 37 | } 38 | 39 | /** 40 | * Returns the default audience for the tokens 41 | * 42 | * @return string 43 | */ 44 | protected function getTokenAudience(): string 45 | { 46 | /** @var string $audience */ 47 | $audience = envValue('TOKEN_AUDIENCE', 'https://phalcon.io'); 48 | 49 | return $audience; 50 | } 51 | 52 | /** 53 | * Returns the time the token is issued at 54 | * 55 | * @return int 56 | */ 57 | protected function getTokenTimeIssuedAt(): int 58 | { 59 | return time(); 60 | } 61 | 62 | /** 63 | * Returns the time drift i.e. token will be valid not before 64 | * 65 | * @return int 66 | */ 67 | protected function getTokenTimeNotBefore(): int 68 | { 69 | return (time() + envValue('TOKEN_NOT_BEFORE', 0)); 70 | } 71 | 72 | /** 73 | * Returns the expiry time for the token 74 | * 75 | * @return int 76 | */ 77 | protected function getTokenTimeExpiration(): int 78 | { 79 | return (time() + envValue('TOKEN_EXPIRATION', 86400)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/Transformers/BaseTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Transformers; 15 | 16 | use League\Fractal\Resource\Collection; 17 | use League\Fractal\Resource\Item; 18 | use League\Fractal\TransformerAbstract; 19 | use Phalcon\Api\Exception\ModelException; 20 | use Phalcon\Api\Mvc\Model\AbstractModel; 21 | 22 | use function array_intersect; 23 | use function array_keys; 24 | 25 | /** 26 | * Class BaseTransformer 27 | */ 28 | class BaseTransformer extends TransformerAbstract 29 | { 30 | /** @var array */ 31 | private array $fields = []; 32 | 33 | /** @var string */ 34 | private string $resource = ''; 35 | 36 | /** 37 | * BaseTransformer constructor. 38 | * 39 | * @param array $fields 40 | * @param string $resource 41 | */ 42 | public function __construct(array $fields = [], string $resource = '') 43 | { 44 | $this->fields = $fields; 45 | $this->resource = $resource; 46 | } 47 | 48 | /** 49 | * @param AbstractModel $model 50 | * 51 | * @return array 52 | * @throws ModelException 53 | */ 54 | public function transform(AbstractModel $model): array 55 | { 56 | $modelFields = array_keys($model->getModelFilters()); 57 | $requestedFields = $this->fields[$this->resource] ?? $modelFields; 58 | $fields = array_intersect($modelFields, $requestedFields); 59 | $data = []; 60 | foreach ($fields as $field) { 61 | $data[$field] = $model->get($field); 62 | } 63 | 64 | return $data; 65 | } 66 | 67 | /** 68 | * @param string $method 69 | * @param AbstractModel $model 70 | * @param string $transformer 71 | * @param string $resource 72 | * 73 | * @return Collection|Item 74 | */ 75 | protected function getRelatedData( 76 | string $method, 77 | AbstractModel $model, 78 | string $transformer, 79 | string $resource 80 | ): Collection|Item { 81 | /** @var AbstractModel $data */ 82 | $data = $model->getRelated($resource); 83 | 84 | return $this->$method( 85 | $data, 86 | new $transformer($this->fields, $resource), 87 | $resource 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /library/Transformers/CompaniesTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Transformers; 15 | 16 | use League\Fractal\Resource\Collection; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\Companies; 19 | 20 | /** 21 | * Class CompaniesTransformer 22 | */ 23 | class CompaniesTransformer extends BaseTransformer 24 | { 25 | /** 26 | * @var array 27 | */ 28 | protected array $availableIncludes = [ 29 | Relationships::PRODUCTS, 30 | Relationships::INDIVIDUALS, 31 | ]; 32 | 33 | /** 34 | * @param Companies $company 35 | * 36 | * @return Collection 37 | */ 38 | public function includeIndividuals(Companies $company): Collection 39 | { 40 | return $this->getRelatedData( 41 | 'collection', 42 | $company, 43 | IndividualsTransformer::class, 44 | Relationships::INDIVIDUALS 45 | ); 46 | } 47 | 48 | /** 49 | * @param Companies $company 50 | * 51 | * @return Collection 52 | */ 53 | public function includeProducts(Companies $company): Collection 54 | { 55 | return $this->getRelatedData( 56 | 'collection', 57 | $company, 58 | ProductsTransformer::class, 59 | Relationships::PRODUCTS 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /library/Transformers/IndividualTypesTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Transformers; 15 | 16 | use League\Fractal\Resource\Collection; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\IndividualTypes; 19 | 20 | /** 21 | * Class IndividualTypesTransformer 22 | */ 23 | class IndividualTypesTransformer extends BaseTransformer 24 | { 25 | /** @var array */ 26 | protected array $availableIncludes = [ 27 | Relationships::INDIVIDUALS, 28 | ]; 29 | 30 | /** @var string */ 31 | protected string $resource = Relationships::INDIVIDUAL_TYPES; 32 | 33 | /** 34 | * @param IndividualTypes $type 35 | * 36 | * @return Collection 37 | */ 38 | public function includeIndividuals(IndividualTypes $type): Collection 39 | { 40 | return $this->getRelatedData( 41 | 'collection', 42 | $type, 43 | IndividualsTransformer::class, 44 | Relationships::INDIVIDUALS 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/Transformers/IndividualsTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Transformers; 15 | 16 | use League\Fractal\Resource\Item; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\Individuals; 19 | 20 | /** 21 | * Class IndividualsTransformer 22 | */ 23 | class IndividualsTransformer extends BaseTransformer 24 | { 25 | /** @var array */ 26 | protected array $availableIncludes = [ 27 | Relationships::COMPANIES, 28 | Relationships::INDIVIDUAL_TYPES, 29 | ]; 30 | 31 | /** @var string */ 32 | protected string $resource = Relationships::INDIVIDUALS; 33 | 34 | /** 35 | * Includes the companies 36 | * 37 | * @param Individuals $individual 38 | * 39 | * @return Item 40 | */ 41 | public function includeCompanies(Individuals $individual): Item 42 | { 43 | return $this->getRelatedData( 44 | 'item', 45 | $individual, 46 | CompaniesTransformer::class, 47 | Relationships::COMPANIES 48 | ); 49 | } 50 | 51 | /** 52 | * Includes the product types 53 | * 54 | * @param Individuals $individual 55 | * 56 | * @return Item 57 | */ 58 | public function includeIndividualTypes(Individuals $individual): Item 59 | { 60 | return $this->getRelatedData( 61 | 'item', 62 | $individual, 63 | BaseTransformer::class, 64 | Relationships::INDIVIDUAL_TYPES 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /library/Transformers/ProductTypesTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Transformers; 15 | 16 | use League\Fractal\Resource\Collection; 17 | use Phalcon\Api\Constants\Relationships; 18 | use Phalcon\Api\Models\ProductTypes; 19 | 20 | /** 21 | * Class ProductTypesTransformer 22 | */ 23 | class ProductTypesTransformer extends BaseTransformer 24 | { 25 | protected array $availableIncludes = [ 26 | Relationships::PRODUCTS, 27 | ]; 28 | 29 | /** 30 | * @param ProductTypes $type 31 | * 32 | * @return Collection 33 | */ 34 | public function includeProducts(ProductTypes $type): Collection 35 | { 36 | return $this->getRelatedData( 37 | 'collection', 38 | $type, 39 | ProductsTransformer::class, 40 | Relationships::PRODUCTS 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /library/Transformers/ProductsTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Transformers; 15 | 16 | use League\Fractal\Resource\Collection; 17 | use League\Fractal\Resource\Item; 18 | use Phalcon\Api\Constants\Relationships; 19 | use Phalcon\Api\Models\Products; 20 | 21 | /** 22 | * Class ProductsTransformer 23 | */ 24 | class ProductsTransformer extends BaseTransformer 25 | { 26 | protected array $availableIncludes = [ 27 | Relationships::COMPANIES, 28 | Relationships::PRODUCT_TYPES, 29 | ]; 30 | 31 | /** 32 | * Includes the companies 33 | * 34 | * @param Products $product 35 | * 36 | * @return Collection 37 | */ 38 | public function includeCompanies(Products $product): Collection 39 | { 40 | return $this->getRelatedData( 41 | 'collection', 42 | $product, 43 | CompaniesTransformer::class, 44 | Relationships::COMPANIES 45 | ); 46 | } 47 | 48 | /** 49 | * Includes the product types 50 | * 51 | * @param Products $product 52 | * 53 | * @return Item 54 | */ 55 | public function includeProductTypes(Products $product): Item 56 | { 57 | return $this->getRelatedData( 58 | 'item', 59 | $product, 60 | BaseTransformer::class, 61 | Relationships::PRODUCT_TYPES 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /library/Validation/CompaniesValidator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Phalcon\Api\Validation; 15 | 16 | use Phalcon\Filter\Filter; 17 | use Phalcon\Filter\Validation; 18 | use Phalcon\Filter\Validation\Validator\PresenceOf; 19 | 20 | class CompaniesValidator extends Validation 21 | { 22 | /** 23 | * @return void 24 | */ 25 | public function initialize(): void 26 | { 27 | $presenceOf = new PresenceOf( 28 | [ 29 | 'message' => "The company name is required", 30 | ] 31 | ); 32 | $this->setFilters('name', Filter::FILTER_STRING); 33 | $this->add('name', $presenceOf); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /phinx.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'migrations' => appPath('/storage/db/migrations'), 12 | 'seeds' => appPath('/storage/db/seeds'), 13 | ], 14 | 'environments' => [ 15 | 'default_migration_table' => 'ut_migrations', 16 | 'default_database' => 'development', 17 | 'production' => [ 18 | 'adapter' => 'mysql', 19 | 'host' => 'localhost', 20 | 'name' => 'tdm_prod', 21 | 'user' => 'root', 22 | 'pass' => 'password', 23 | 'port' => 3306, 24 | 'charset' => 'utf8', 25 | ], 26 | 'development' => [ 27 | 'adapter' => envValue('DATA_API_MYSQL_ADAPTER', 'mysql'), 28 | 'host' => envValue('DATA_API_MYSQL_HOST', '127.0.0.1'), 29 | 'name' => envValue('DATA_API_MYSQL_NAME', 'phalcon_api'), 30 | 'user' => envValue('DATA_API_MYSQL_USER', 'root'), 31 | 'pass' => envValue('DATA_API_MYSQL_PASS', 'password'), 32 | 'port' => envValue('DATA_API_MYSQL_PORT', 3306), 33 | 'charset' => envValue('DATA_TDM_MYSQL_CHARSET', 'utf8'), 34 | ], 35 | 'testing' => [ 36 | 'adapter' => envValue('DATA_API_MYSQL_ADAPTER', 'mysql'), 37 | 'host' => envValue('DATA_API_MYSQL_HOST', '127.0.0.1'), 38 | 'name' => envValue('DATA_API_MYSQL_NAME', 'phalcon_api'), 39 | 'user' => envValue('DATA_API_MYSQL_USER', 'root'), 40 | 'pass' => envValue('DATA_API_MYSQL_PASS', 'password'), 41 | 'port' => envValue('DATA_API_MYSQL_PORT', 3306), 42 | 'charset' => envValue('DATA_TDM_MYSQL_CHARSET', 'utf8'), 43 | ], 44 | ], 45 | 'version_order' => 'creation', 46 | ]; 47 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Phalcon Coding Standards 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | */public/* 13 | */_output/* 14 | */_support/* 15 | ./api 16 | ./library 17 | ./tests 18 | 19 | -------------------------------------------------------------------------------- /psalm.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /runCli: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EXEC_PATH="`dirname \"$0\"`" 4 | 5 | PHP_BINARY="`which php`" 6 | $PHP_BINARY $EXEC_PATH/cli/cli.php $* -------------------------------------------------------------------------------- /runTests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./vendor/bin/codecept run --coverage --coverage-html --coverage-xml 4 | -------------------------------------------------------------------------------- /storage/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/cache/metadata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/ci/.env.example: -------------------------------------------------------------------------------- 1 | APP_DEBUG=true 2 | APP_ENV=development 3 | APP_URL=http://api.phalcon.ld 4 | APP_NAME="Phalcon API" 5 | APP_BASE_URI=/ 6 | APP_SUPPORT_EMAIL=team@phalcon.io 7 | APP_TIMEZONE=UTC 8 | 9 | TOKEN_AUDIENCE=https://phalcon.io 10 | 11 | CACHE_PREFIX=api_cache_ 12 | CACHE_LIFETIME=86400 13 | 14 | #DATA_API_MEMCACHED_HOST=127.0.0.1 15 | #DATA_API_MEMCACHED_PORT=11211 16 | #DATA_API_MEMCACHED_WEIGHT=100 17 | 18 | DATA_API_MYSQL_NAME=gonano 19 | #DATA_API_MYSQL_USER=root 20 | #DATA_API_MYSQL_PASS=password 21 | 22 | APP_IP=api.phalcon.ld 23 | 24 | LOGGER_DEFAULT_FILENAME=api 25 | 26 | VERSION=20180401 27 | -------------------------------------------------------------------------------- /storage/ci/.env.prod: -------------------------------------------------------------------------------- 1 | APP_DEBUG=false 2 | APP_ENV=production 3 | APP_URL=http://api.phalcon.ld 4 | APP_NAME="Phalcon API" 5 | APP_BASE_URI=/ 6 | APP_SUPPORT_EMAIL=team@phalcon.io 7 | APP_TIMEZONE=UTC 8 | 9 | TOKEN_AUDIENCE=https://phalcon.io 10 | 11 | CACHE_PREFIX=api_cache_ 12 | CACHE_LIFETIME=86400 13 | 14 | #DATA_API_MEMCACHED_HOST=127.0.0.1 15 | #DATA_API_MEMCACHED_PORT=11211 16 | #DATA_API_MEMCACHED_WEIGHT=100 17 | 18 | DATA_API_MYSQL_NAME=gonano 19 | #DATA_API_MYSQL_USER=root 20 | #DATA_API_MYSQL_PASS=password 21 | 22 | APP_IP=api.phalcon.ld 23 | 24 | LOGGER_DEFAULT_FILENAME=api 25 | 26 | VERSION=20180401 27 | -------------------------------------------------------------------------------- /storage/ci/.htaccess.txt: -------------------------------------------------------------------------------- 1 | AddDefaultCharset UTF-8 2 | 3 | 4 | RewriteEngine On 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L] 8 | 9 | -------------------------------------------------------------------------------- /storage/ci/phinx.php.example: -------------------------------------------------------------------------------- 1 | [ 5 | 'migrations' => getenv('PHINX_CONFIG_DIR') . 'storage/db/migrations', 6 | 'seeds' => getenv('PHINX_CONFIG_DIR') . 'storage/db/seeds', 7 | ], 8 | 'environments' => [ 9 | 'default_migration_table' => 'ut_migrations', 10 | 'default_database' => 'development', 11 | 'production' => [ 12 | 'adapter' => 'mysql', 13 | 'host' => getenv('DATA_API_MYSQL_HOST'), 14 | 'name' => getenv('DATA_API_MYSQL_NAME'), 15 | 'user' => getenv('DATA_API_MYSQL_USER'), 16 | 'pass' => getenv('DATA_API_MYSQL_PASS'), 17 | 'port' => 3306, 18 | 'charset' => 'utf8', 19 | ], 20 | 'development' => [ 21 | 'adapter' => 'mysql', 22 | 'host' => getenv('DATA_API_MYSQL_HOST'), 23 | 'name' => getenv('DATA_API_MYSQL_NAME'), 24 | 'user' => getenv('DATA_API_MYSQL_USER'), 25 | 'pass' => getenv('DATA_API_MYSQL_PASS'), 26 | 'port' => 3306, 27 | 'charset' => 'utf8', 28 | ], 29 | ], 30 | 'version_order' => 'creation', 31 | ]; 32 | -------------------------------------------------------------------------------- /storage/ci/phinx.php.prod: -------------------------------------------------------------------------------- 1 | [ 5 | 'migrations' => getenv('PHINX_CONFIG_DIR') . 'storage/db/migrations', 6 | 'seeds' => getenv('PHINX_CONFIG_DIR') . 'storage/db/seeds', 7 | ], 8 | 'environments' => [ 9 | 'default_migration_table' => 'ut_migrations', 10 | 'default_database' => 'development', 11 | 'production' => [ 12 | 'adapter' => 'mysql', 13 | 'host' => getenv('DATA_API_MYSQL_HOST'), 14 | 'name' => 'gonano', 15 | 'user' => getenv('DATA_API_MYSQL_USER'), 16 | 'pass' => getenv('DATA_API_MYSQL_PASS'), 17 | 'port' => 3306, 18 | 'charset' => 'utf8', 19 | ], 20 | 'development' => [ 21 | 'adapter' => 'mysql', 22 | 'host' => getenv('DATA_API_MYSQL_HOST'), 23 | 'name' => 'gonano', 24 | 'user' => getenv('DATA_API_MYSQL_USER'), 25 | 'pass' => getenv('DATA_API_MYSQL_PASS'), 26 | 'port' => 3306, 27 | 'charset' => 'utf8', 28 | ], 29 | ], 30 | 'version_order' => 'creation', 31 | ]; 32 | -------------------------------------------------------------------------------- /storage/ci/xdebug.ini: -------------------------------------------------------------------------------- 1 | xdebug.coverage_enable = 1 2 | xdebug.default_enable = 1 3 | xdebug.extended_info = 1 4 | xdebug.force_display_errors = 0 5 | xdebug.force_error_reporting = 0 6 | xdebug.max_nesting_level = 256 7 | xdebug.profiler_enable = 1 8 | xdebug.profiler_enable_trigger = 1 9 | xdebug.profiler_output_dir = "/tmp" 10 | xdebug.remote_autostart = 0 11 | xdebug.remote_connect_back = 1 12 | xdebug.remote_cookie_expire_time = 3600 13 | xdebug.remote_enable = 1 14 | xdebug.remote_handler = "dbgp" 15 | xdebug.remote_host = "localhost" 16 | xdebug.remote_log = "" 17 | xdebug.remote_mode = "req" 18 | xdebug.remote_port = 9090 19 | xdebug.show_error_trace = 1 20 | xdebug.show_exception_trace = 1 21 | xdebug.show_local_vars = 1 22 | xdebug.var_display_max_children = 128 23 | xdebug.var_display_max_data = 512 24 | xdebug.var_display_max_depth = 3 25 | -------------------------------------------------------------------------------- /storage/config/.env.ci: -------------------------------------------------------------------------------- 1 | APP_DEBUG=true 2 | APP_ENV=development 3 | APP_URL=http://api.phalcon.ld 4 | APP_NAME="Phalcon API" 5 | APP_BASE_URI=/ 6 | APP_SUPPORT_EMAIL=team@phalcon.io 7 | APP_TIMEZONE=UTC 8 | 9 | DATA_API_MYSQL_HOST=127.0.0.1 10 | DATA_API_MYSQL_USER=root 11 | DATA_API_MYSQL_PASS=secret 12 | DATA_API_MYSQL_NAME=phalcon_api 13 | 14 | DATA_API_REDIS_HOST=127.0.0.1 15 | 16 | TOKEN_AUDIENCE=https://phalcon.io 17 | 18 | CACHE_PREFIX=api_cache_ 19 | CACHE_LIFETIME=86400 20 | 21 | APP_IP=127.0.0.1 22 | 23 | LOGGER_DEFAULT_FILENAME=api 24 | 25 | VERSION=20180401 26 | 27 | CODECEPTION_URL=127.0.0.1 28 | CODECEPTION_PORT=80 29 | -------------------------------------------------------------------------------- /storage/config/.env.test: -------------------------------------------------------------------------------- 1 | APP_DEBUG=true 2 | APP_ENV=development 3 | APP_URL=http://api.phalcon.ld 4 | APP_NAME="Phalcon API" 5 | APP_BASE_URI=/ 6 | APP_SUPPORT_EMAIL=team@phalcon.io 7 | APP_TIMEZONE=UTC 8 | 9 | DATA_API_MYSQL_HOST=rest-api-mysql 10 | DATA_API_MYSQL_USER=phalcon 11 | DATA_API_MYSQL_PASS=secret 12 | DATA_API_MYSQL_NAME=phalcon_api 13 | 14 | DATA_API_REDIS_HOST=rest-api-cache 15 | 16 | TOKEN_AUDIENCE=https://phalcon.io 17 | 18 | CACHE_PREFIX=api_cache_ 19 | CACHE_LIFETIME=86400 20 | 21 | APP_IP=api.phalcon.ld 22 | 23 | LOGGER_DEFAULT_FILENAME=api 24 | 25 | VERSION=20180401 26 | 27 | CODECEPTION_URL=rest-api-nginx-8.0 28 | CODECEPTION_PORT=80 29 | -------------------------------------------------------------------------------- /storage/config/extra.ini: -------------------------------------------------------------------------------- 1 | error_reporting=E_ALL 2 | display_errors="On" 3 | display_startup_errors="On" 4 | log_errors="On" 5 | error_log=/code/storage/logs/php_errors.log 6 | memory_limit=512M 7 | apc.enable_cli="On" 8 | session.save_path="/tmp" 9 | -------------------------------------------------------------------------------- /storage/config/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | general_log = 1 3 | general_log_file = /var/lib/mysql/general.log 4 | -------------------------------------------------------------------------------- /storage/config/nginx/8.0/app-8.0.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | index index.php index.html; 4 | error_log /var/www/storage/logs/nginx-error.log; 5 | access_log /var/www/storage/logs/nginx-access.log; 6 | root /var/www/api/public; 7 | location ~ \.php$ { 8 | try_files $uri =404; 9 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 10 | fastcgi_pass rest-api-app-8.0:9000; 11 | fastcgi_index index.php; 12 | include fastcgi_params; 13 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 14 | fastcgi_param PATH_INFO $fastcgi_path_info; 15 | } 16 | location / { 17 | try_files $uri $uri/ /index.php?$query_string; 18 | gzip_static on; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /storage/config/nginx/8.1/app-8.1.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | index index.php index.html; 4 | error_log /var/www/storage/logs/nginx-error.log; 5 | access_log /var/www/storage/logs/nginx-access.log; 6 | root /var/www/api/public; 7 | location ~ \.php$ { 8 | try_files $uri =404; 9 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 10 | fastcgi_pass rest-api-app-8.1:9000; 11 | fastcgi_index index.php; 12 | include fastcgi_params; 13 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 14 | fastcgi_param PATH_INFO $fastcgi_path_info; 15 | } 16 | location / { 17 | try_files $uri $uri/ /index.php?$query_string; 18 | gzip_static on; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /storage/db/migrations/20180508203845_add_users_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_users', 12 | [ 13 | 'id' => 'usr_id', 14 | 'signed' => false, 15 | ] 16 | ); 17 | 18 | $table 19 | ->addColumn( 20 | 'usr_status_flag', 21 | 'boolean', 22 | [ 23 | 'signed' => false, 24 | 'null' => false, 25 | 'default' => 0, 26 | ] 27 | ) 28 | ->addColumn( 29 | 'usr_username', 30 | 'string', 31 | [ 32 | 'limit' => 128, 33 | 'null' => false, 34 | 'default' => '', 35 | ] 36 | ) 37 | ->addColumn( 38 | 'usr_password', 39 | 'string', 40 | [ 41 | 'limit' => 128, 42 | 'null' => false, 43 | 'default' => '', 44 | ] 45 | ) 46 | ->addIndex('usr_status_flag') 47 | ->addIndex('usr_username') 48 | ->addIndex('usr_password') 49 | ->save(); 50 | 51 | $this->execute( 52 | 'ALTER TABLE co_users ' . 53 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 54 | ); 55 | } 56 | 57 | public function down() 58 | { 59 | $this->table('co_users')->drop()->save(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /storage/db/migrations/20180509232740_add_token_and_audience_fields_to_users.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->addColumn( 13 | 'usr_domain_name', 14 | 'string', 15 | [ 16 | 'limit' => 128, 17 | 'null' => false, 18 | 'default' => '', 19 | ] 20 | ) 21 | ->addColumn( 22 | 'usr_token', 23 | 'string', 24 | [ 25 | 'limit' => 128, 26 | 'null' => false, 27 | 'default' => '', 28 | ] 29 | ) 30 | ->addIndex('usr_token') 31 | ->save(); 32 | } 33 | 34 | public function down() 35 | { 36 | $table = $this->table('co_users'); 37 | $table 38 | ->removeColumn('usr_domain_name') 39 | ->removeColumn('usr_token') 40 | ->save(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /storage/db/migrations/20180510004748_add_token_id_in_users.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->addColumn( 13 | 'usr_token_id', 14 | 'string', 15 | [ 16 | 'limit' => 128, 17 | 'null' => false, 18 | 'default' => '', 19 | ] 20 | ) 21 | ->addIndex('usr_token_id') 22 | ->save(); 23 | } 24 | 25 | public function down() 26 | { 27 | $table = $this->table('co_users'); 28 | $table 29 | ->removeIndex('usr_token_id') 30 | ->removeColumn('usr_token_id') 31 | ->save(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /storage/db/migrations/20180525210751_increase_token_size.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->changeColumn( 13 | 'usr_token', 14 | 'string', 15 | [ 16 | 'limit' => 256, 17 | 'null' => false, 18 | 'default' => '', 19 | ] 20 | ) 21 | ->save(); 22 | } 23 | 24 | public function down() 25 | { 26 | $table = $this->table('co_users'); 27 | $table 28 | ->changeColumn( 29 | 'usr_token', 30 | 'string', 31 | [ 32 | 'limit' => 128, 33 | 'null' => false, 34 | 'default' => '', 35 | ] 36 | ) 37 | ->save(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /storage/db/migrations/20180528011826_split_token_field.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->renameColumn('usr_token', 'usr_token_pre') 13 | ->addColumn( 14 | 'usr_token_mid', 15 | 'string', 16 | [ 17 | 'limit' => 256, 18 | 'null' => false, 19 | 'default' => '', 20 | 'after' => 'usr_token_pre', 21 | ] 22 | ) 23 | ->addColumn( 24 | 'usr_token_post', 25 | 'string', 26 | [ 27 | 'limit' => 256, 28 | 'null' => false, 29 | 'default' => '', 30 | 'after' => 'usr_token_mid', 31 | ] 32 | ) 33 | ->addIndex('usr_token_mid') 34 | ->addIndex('usr_token_post') 35 | ->save(); 36 | } 37 | 38 | public function down() 39 | { 40 | $table = $this->table('co_users'); 41 | $table 42 | ->renameColumn('usr_token_pre', 'usr_token') 43 | ->removeColumn('usr_token_mid') 44 | ->removeColumn('usr_token_post') 45 | ->save(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /storage/db/migrations/20180604160513_add_token_password_in_users.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->addColumn( 13 | 'usr_token_password', 14 | 'string', 15 | [ 16 | 'limit' => 64, 17 | 'null' => false, 18 | 'default' => '', 19 | 'after' => 'usr_domain_name', 20 | ] 21 | ) 22 | ->save(); 23 | } 24 | 25 | public function down() 26 | { 27 | $table = $this->table('co_users'); 28 | $table 29 | ->removeColumn('usr_token_password') 30 | ->save(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /storage/db/migrations/20180607165746_remove_token_fields_from_users.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->removeColumn('usr_token_pre') 13 | ->removeColumn('usr_token_mid') 14 | ->removeColumn('usr_token_post') 15 | ->save(); 16 | } 17 | 18 | public function down() 19 | { 20 | $table = $this->table('co_users'); 21 | $table 22 | ->addColumn( 23 | 'usr_token_pre', 24 | 'string', 25 | [ 26 | 'limit' => 256, 27 | 'null' => false, 28 | 'default' => '', 29 | 'after' => 'usr_password', 30 | ] 31 | ) 32 | ->addColumn( 33 | 'usr_token_mid', 34 | 'string', 35 | [ 36 | 'limit' => 256, 37 | 'null' => false, 38 | 'default' => '', 39 | 'after' => 'usr_token_pre', 40 | ] 41 | ) 42 | ->addColumn( 43 | 'usr_token_post', 44 | 'string', 45 | [ 46 | 'limit' => 256, 47 | 'null' => false, 48 | 'default' => '', 49 | 'after' => 'usr_token_mid', 50 | ] 51 | ) 52 | ->addIndex('usr_token_pre') 53 | ->addIndex('usr_token_mid') 54 | ->addIndex('usr_token_post') 55 | ->save(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /storage/db/migrations/20180612191624_rename_domain_to_issuer.php: -------------------------------------------------------------------------------- 1 | table('co_users'); 11 | $table 12 | ->renameColumn('usr_domain_name', 'usr_issuer') 13 | ->save(); 14 | 15 | $table 16 | ->addIndex('usr_issuer') 17 | ->save(); 18 | } 19 | 20 | public function down() 21 | { 22 | $table = $this->table('co_users'); 23 | $table 24 | ->removeIndex('usr_issuer') 25 | ->save(); 26 | 27 | $table 28 | ->renameColumn('usr_issuer', 'usr_domain_name') 29 | ->save(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /storage/db/migrations/20180715202028_add_companies_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_companies', 12 | [ 13 | 'id' => 'com_id', 14 | 'signed' => false, 15 | ] 16 | ); 17 | 18 | $table 19 | ->addColumn( 20 | 'com_name', 21 | 'string', 22 | [ 23 | 'limit' => 128, 24 | 'null' => false, 25 | 'default' => '', 26 | ] 27 | ) 28 | ->addColumn( 29 | 'com_address', 30 | 'string', 31 | [ 32 | 'limit' => 128, 33 | 'null' => false, 34 | 'default' => '', 35 | ] 36 | ) 37 | ->addColumn( 38 | 'com_city', 39 | 'string', 40 | [ 41 | 'limit' => 64, 42 | 'null' => false, 43 | 'default' => '', 44 | ] 45 | ) 46 | ->addColumn( 47 | 'com_telephone', 48 | 'string', 49 | [ 50 | 'limit' => 24, 51 | 'null' => false, 52 | 'default' => '', 53 | ] 54 | ) 55 | ->addIndex('com_name') 56 | ->addIndex('com_city') 57 | ->save(); 58 | 59 | $this->execute( 60 | 'ALTER TABLE co_companies ' . 61 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 62 | ); 63 | } 64 | 65 | public function down() 66 | { 67 | $this->table('co_companies')->drop()->save(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /storage/db/migrations/20180715221034_add_products_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_products', 12 | [ 13 | 'id' => 'prd_id', 14 | 'signed' => false, 15 | ] 16 | ); 17 | 18 | $table 19 | ->addColumn( 20 | 'prd_name', 21 | 'string', 22 | [ 23 | 'limit' => 128, 24 | 'null' => false, 25 | 'default' => '', 26 | ] 27 | ) 28 | ->addColumn( 29 | 'prd_description', 30 | 'string', 31 | [ 32 | 'limit' => 256, 33 | 'null' => false, 34 | 'default' => '', 35 | ] 36 | ) 37 | ->addColumn( 38 | 'prd_quantity', 39 | 'integer', 40 | [ 41 | 'limit' => 11, 42 | 'null' => false, 43 | 'signed' => false, 44 | 'default' => 0, 45 | ] 46 | ) 47 | ->addColumn( 48 | 'prd_price', 49 | 'decimal', 50 | [ 51 | 'precision' => 10, 52 | 'scale' => 2, 53 | 'null' => false, 54 | 'signed' => false, 55 | 'default' => 0, 56 | ] 57 | ) 58 | ->addIndex('prd_name') 59 | ->addIndex('prd_quantity') 60 | ->addIndex('prd_price') 61 | ->save(); 62 | 63 | $this->execute( 64 | 'ALTER TABLE co_products ' . 65 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 66 | ); 67 | } 68 | 69 | public function down() 70 | { 71 | $this->table('co_products')->drop()->save(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /storage/db/migrations/20180717231009_add_product_types_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_product_types', 12 | [ 13 | 'id' => 'prt_id', 14 | 'signed' => false, 15 | ] 16 | ); 17 | 18 | $table 19 | ->addColumn( 20 | 'prt_name', 21 | 'string', 22 | [ 23 | 'limit' => 128, 24 | 'null' => false, 25 | 'default' => '', 26 | ] 27 | ) 28 | ->addColumn( 29 | 'prt_description', 30 | 'string', 31 | [ 32 | 'limit' => 256, 33 | 'null' => false, 34 | 'default' => '', 35 | ] 36 | ) 37 | ->addIndex('prt_name') 38 | ->save(); 39 | 40 | $this->execute( 41 | 'ALTER TABLE co_product_types ' . 42 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 43 | ); 44 | } 45 | 46 | public function down() 47 | { 48 | $this->table('co_product_types')->drop()->save(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /storage/db/migrations/20180717231024_add_individuals_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_individuals', 12 | [ 13 | 'id' => 'ind_id', 14 | 'signed' => false, 15 | ] 16 | ); 17 | $table 18 | ->addColumn( 19 | 'ind_com_id', 20 | 'integer', 21 | [ 22 | 'signed' => false, 23 | 'limit' => 11, 24 | 'null' => false, 25 | 'default' => 0, 26 | ] 27 | ) 28 | ->addColumn( 29 | 'ind_idt_id', 30 | 'integer', 31 | [ 32 | 'signed' => false, 33 | 'limit' => 11, 34 | 'null' => false, 35 | 'default' => 0, 36 | ] 37 | ) 38 | ->addColumn( 39 | 'ind_name_prefix', 40 | 'string', 41 | [ 42 | 'limit' => 16, 43 | 'null' => false, 44 | 'default' => '', 45 | ] 46 | ) 47 | ->addColumn( 48 | 'ind_name_first', 49 | 'string', 50 | [ 51 | 'limit' => 64, 52 | 'null' => false, 53 | 'default' => '', 54 | ] 55 | ) 56 | ->addColumn( 57 | 'ind_name_middle', 58 | 'string', 59 | [ 60 | 'limit' => 64, 61 | 'null' => false, 62 | 'default' => '', 63 | ] 64 | ) 65 | ->addColumn( 66 | 'ind_name_last', 67 | 'string', 68 | [ 69 | 'limit' => 128, 70 | 'null' => false, 71 | 'default' => '', 72 | ] 73 | ) 74 | ->addColumn( 75 | 'ind_name_suffix', 76 | 'string', 77 | [ 78 | 'limit' => 16, 79 | 'null' => false, 80 | 'default' => '', 81 | ] 82 | ) 83 | ->addIndex('ind_com_id') 84 | ->addIndex('ind_idt_id') 85 | ->addIndex('ind_name_first') 86 | ->addIndex('ind_name_middle') 87 | ->addIndex('ind_name_last') 88 | ->save(); 89 | 90 | $this->execute( 91 | 'ALTER TABLE co_individuals ' . 92 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 93 | ); 94 | } 95 | 96 | public function down() 97 | { 98 | $this->table('co_individuals')->drop()->save(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /storage/db/migrations/20180717231029_add_individual_types_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_individual_types', 12 | [ 13 | 'id' => 'idt_id', 14 | 'signed' => false, 15 | ] 16 | ); 17 | 18 | $table 19 | ->addColumn( 20 | 'idt_name', 21 | 'string', 22 | [ 23 | 'limit' => 128, 24 | 'null' => false, 25 | 'default' => '', 26 | ] 27 | ) 28 | ->addColumn( 29 | 'idt_description', 30 | 'string', 31 | [ 32 | 'limit' => 256, 33 | 'null' => false, 34 | 'default' => '', 35 | ] 36 | ) 37 | ->addIndex('idt_name') 38 | ->save(); 39 | 40 | $this->execute( 41 | 'ALTER TABLE co_individual_types ' . 42 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 43 | ); 44 | } 45 | 46 | public function down() 47 | { 48 | $this->table('co_individual_types')->drop()->save(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /storage/db/migrations/20180717231052_add_companies_to_products_table.php: -------------------------------------------------------------------------------- 1 | table( 11 | 'co_companies_x_products', 12 | [ 13 | 'id' => false, 14 | 'primary_key' => [ 15 | 'cxp_com_id', 16 | 'cxp_prd_id', 17 | ], 18 | ] 19 | ); 20 | 21 | $table 22 | ->addColumn( 23 | 'cxp_com_id', 24 | 'integer', 25 | [ 26 | 'signed' => false, 27 | 'limit' => 11, 28 | 'null' => false, 29 | 'default' => 0, 30 | ] 31 | ) 32 | ->addColumn( 33 | 'cxp_prd_id', 34 | 'integer', 35 | [ 36 | 'signed' => false, 37 | 'limit' => 11, 38 | 'null' => false, 39 | 'default' => 0, 40 | ] 41 | ) 42 | ->addIndex('cxp_com_id') 43 | ->addIndex('cxp_prd_id') 44 | ->save(); 45 | 46 | $this->execute( 47 | 'ALTER TABLE co_companies_x_products ' . 48 | 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' 49 | ); 50 | } 51 | 52 | public function down() 53 | { 54 | $this->table('co_companies_x_products')->drop()->save(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /storage/db/migrations/20180717232102_add_product_type_to_products.php: -------------------------------------------------------------------------------- 1 | table('co_products'); 11 | 12 | $table 13 | ->addColumn( 14 | 'prd_prt_id', 15 | 'integer', 16 | [ 17 | 'signed' => false, 18 | 'limit' => 11, 19 | 'null' => false, 20 | 'default' => 0, 21 | 'after' => 'prd_id', 22 | ] 23 | ) 24 | ->addIndex('prd_id') 25 | ->save(); 26 | } 27 | 28 | public function down() 29 | { 30 | $table = $this->table('co_products'); 31 | $table->removeColumn('prd_prt_id')->save(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | assertRegExp($regexp, $content); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/_support/Helper/Cli.php: -------------------------------------------------------------------------------- 1 | addApiUserRecord(); 25 | $token = $I->apiLogin(); 26 | $name = uniqid('com'); 27 | 28 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 29 | $I->sendPOST( 30 | Data::$companiesUrl, 31 | Data::companyAddJson( 32 | $name, 33 | '123 Phalcon way', 34 | 'World', 35 | '555-444-7777' 36 | ) 37 | ); 38 | $I->deleteHeader('Authorization'); 39 | $I->seeResponseIsSuccessful(Response::CREATED); 40 | 41 | $company = $I->getRecordWithFields( 42 | Companies::class, 43 | [ 44 | 'name' => $name, 45 | ] 46 | ); 47 | $I->assertNotEquals(false, $company); 48 | 49 | $I->seeHttpHeader('Location', appUrl(Relationships::COMPANIES, $company->get('id'))); 50 | $I->seeSuccessJsonResponse( 51 | 'data', 52 | Data::companiesAddResponse($company) 53 | ); 54 | 55 | $I->assertNotEquals(false, $company->delete()); 56 | } 57 | 58 | /** 59 | * @param ApiTester $I 60 | */ 61 | public function addNewCompanyWithExistingName(ApiTester $I) 62 | { 63 | $I->addApiUserRecord(); 64 | $token = $I->apiLogin(); 65 | $name = uniqid('com'); 66 | $I->haveRecordWithFields( 67 | Companies::class, 68 | [ 69 | 'name' => $name, 70 | ] 71 | ); 72 | 73 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 74 | $I->sendPOST( 75 | Data::$companiesUrl, 76 | Data::companyAddJson( 77 | $name, 78 | '123 Phalcon way', 79 | 'World', 80 | '555-444-7777' 81 | ) 82 | ); 83 | $I->deleteHeader('Authorization'); 84 | $I->seeErrorJsonResponse('The company name already exists in the database'); 85 | } 86 | 87 | /** 88 | * @param ApiTester $I 89 | */ 90 | public function addNewCompanyWithoutPostingName(ApiTester $I) 91 | { 92 | $I->addApiUserRecord(); 93 | $token = $I->apiLogin(); 94 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 95 | $I->sendPOST( 96 | Data::$companiesUrl, 97 | Data::companyAddJson( 98 | '', 99 | '123 Phalcon way', 100 | 'World', 101 | '555-444-7777' 102 | ) 103 | ); 104 | $I->deleteHeader('Authorization'); 105 | $I->deleteHeader('Authorization'); 106 | $I->seeErrorJsonResponse('The company name is required'); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/api/Companies/GetBase.php: -------------------------------------------------------------------------------- 1 | addCompanyRecord('com-a'); 14 | $indType = $I->addIndividualTypeRecord('type-a-'); 15 | $indOne = $I->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); 16 | $indTwo = $I->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); 17 | $prdType = $I->addProductTypeRecord('type-a-'); 18 | $prdOne = $I->addProductRecord('prd-a-', $prdType->get('id')); 19 | $prdTwo = $I->addProductRecord('prd-b-', $prdType->get('id')); 20 | $I->addCompanyXProduct($company->get('id'), $prdOne->get('id')); 21 | $I->addCompanyXProduct($company->get('id'), $prdTwo->get('id')); 22 | 23 | return [$company, $prdOne, $prdTwo, $indOne, $indTwo]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/api/Companies/GetCest.php: -------------------------------------------------------------------------------- 1 | addApiUserRecord(); 18 | $token = $I->apiLogin(); 19 | 20 | $company = $I->addCompanyRecord('com-a-'); 21 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 22 | $I->sendGET(sprintf(Data::$companiesRecordUrl, $company->get('id'))); 23 | $I->deleteHeader('Authorization'); 24 | $I->seeResponseIsSuccessful(); 25 | $I->seeSuccessJsonResponse( 26 | 'data', 27 | [ 28 | Data::companiesResponse($company), 29 | ] 30 | ); 31 | } 32 | 33 | /** 34 | * @param ApiTester $I 35 | */ 36 | public function getUnknownCompany(ApiTester $I) 37 | { 38 | $I->addApiUserRecord(); 39 | $token = $I->apiLogin(); 40 | 41 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 42 | $I->sendGET(sprintf(Data::$companiesRecordUrl, 1)); 43 | $I->deleteHeader('Authorization'); 44 | $I->seeResponseIs404(); 45 | } 46 | 47 | /** 48 | * @param ApiTester $I 49 | * 50 | * @throws ModelException 51 | */ 52 | public function getCompanies(ApiTester $I) 53 | { 54 | $I->addApiUserRecord(); 55 | $token = $I->apiLogin(); 56 | 57 | /** @var Companies $comOne */ 58 | $comOne = $I->addCompanyRecord('com-a-'); 59 | /** @var Companies $comTwo */ 60 | $comTwo = $I->addCompanyRecord('com-b-'); 61 | 62 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 63 | $I->sendGET(Data::$companiesUrl); 64 | $I->deleteHeader('Authorization'); 65 | $I->seeResponseIsSuccessful(); 66 | $I->seeSuccessJsonResponse( 67 | 'data', 68 | [ 69 | Data::companiesResponse($comOne), 70 | Data::companiesResponse($comTwo), 71 | ] 72 | ); 73 | } 74 | 75 | /** 76 | * @param ApiTester $I 77 | */ 78 | public function getCompaniesNoData(ApiTester $I) 79 | { 80 | $I->addApiUserRecord(); 81 | $token = $I->apiLogin(); 82 | 83 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 84 | $I->sendGET(Data::$companiesUrl); 85 | $I->deleteHeader('Authorization'); 86 | $I->seeResponseIsSuccessful(); 87 | $I->seeSuccessJsonResponse(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/api/Companies/GetFieldsCest.php: -------------------------------------------------------------------------------- 1 | runTestsCompaniesWithIncludesAndFields($I); 16 | } 17 | 18 | public function getCompaniesWithIncludesAndUnknownFields(ApiTester $I) 19 | { 20 | $this->runTestsCompaniesWithIncludesAndFields($I, ',unknown-product-field'); 21 | } 22 | 23 | private function runTestsCompaniesWithIncludesAndFields(ApiTester $I, string $fields = '') 24 | { 25 | list($com, $prdOne, $prdTwo) = $this->addRecords($I); 26 | 27 | $I->addApiUserRecord(); 28 | $token = $I->apiLogin(); 29 | 30 | $I->haveHttpHeader('Authorization', 'Bearer ' . $token); 31 | $I->sendGET( 32 | sprintf( 33 | Data::$companiesRecordIncludesUrl, 34 | $com->get('id'), 35 | Relationships::PRODUCTS 36 | ) . 37 | '&fields[' . Relationships::COMPANIES . ']=id,name,city' . 38 | '&fields[' . Relationships::PRODUCTS . ']=id,name,price' . $fields 39 | ); 40 | 41 | $I->deleteHeader('Authorization'); 42 | 43 | $I->seeResponseIsSuccessful(); 44 | 45 | $included = []; 46 | $element = [ 47 | 'type' => Relationships::COMPANIES, 48 | 'id' => $com->get('id'), 49 | 'attributes' => [ 50 | 'name' => $com->get('name'), 51 | 'city' => $com->get('city'), 52 | ], 53 | 'links' => [ 54 | 'self' => sprintf( 55 | '%s/%s/%s', 56 | envValue('APP_URL', 'localhost'), 57 | Relationships::COMPANIES, 58 | $com->get('id') 59 | ), 60 | ], 61 | ]; 62 | 63 | $element['relationships'][Relationships::PRODUCTS] = [ 64 | 'links' => [ 65 | 'self' => sprintf( 66 | '%s/%s/%s/relationships/%s', 67 | envValue('APP_URL', 'localhost'), 68 | Relationships::COMPANIES, 69 | $com->get('id'), 70 | Relationships::PRODUCTS 71 | ), 72 | 'related' => sprintf( 73 | '%s/%s/%s/%s', 74 | envValue('APP_URL', 'localhost'), 75 | Relationships::COMPANIES, 76 | $com->get('id'), 77 | Relationships::PRODUCTS 78 | ), 79 | ], 80 | 'data' => [ 81 | [ 82 | 'type' => Relationships::PRODUCTS, 83 | 'id' => $prdOne->get('id'), 84 | ], 85 | [ 86 | 'type' => Relationships::PRODUCTS, 87 | 'id' => $prdTwo->get('id'), 88 | ], 89 | ], 90 | ]; 91 | 92 | $included[] = Data::productFieldsResponse($prdOne); 93 | $included[] = Data::productFieldsResponse($prdTwo); 94 | 95 | $I->seeSuccessJsonResponse('data', [$element]); 96 | 97 | if (count($included) > 0) { 98 | $I->seeSuccessJsonResponse('included', $included); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/api/LoginCest.php: -------------------------------------------------------------------------------- 1 | sendPOST( 17 | Data::$loginUrl, 18 | [ 19 | 'username' => 'user', 20 | 'password' => 'pass', 21 | ] 22 | ); 23 | $I->seeResponseIsSuccessful(); 24 | $I->seeErrorJsonResponse('Incorrect credentials'); 25 | } 26 | 27 | public function loginKnownUser(ApiTester $I) 28 | { 29 | $I->haveRecordWithFields( 30 | Users::class, 31 | [ 32 | 'status' => Flags::ACTIVE, 33 | 'username' => Data::$testUsername, 34 | 'password' => Data::$testPassword, 35 | 'issuer' => Data::$testIssuer, 36 | 'tokenPassword' => Data::$strongPassphrase, 37 | 'tokenId' => Data::$testTokenId, 38 | ] 39 | ); 40 | 41 | $I->sendPOST(Data::$loginUrl, Data::loginJson()); 42 | $I->seeResponseIsSuccessful(); 43 | $response = $I->grabResponse(); 44 | $data = json_decode($response, true); 45 | $I->assertTrue(isset($data['data'])); 46 | $I->assertTrue(isset($data['data']['token'])); 47 | $I->assertTrue(isset($data['meta'])); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/api/NotFoundCest.php: -------------------------------------------------------------------------------- 1 | sendGET(Data::$wrongUrl); 13 | $I->seeResponseIs404(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/api/_bootstrap.php: -------------------------------------------------------------------------------- 1 | runShellCommand('./runCli'); 12 | $I->seeResultCodeIs(0); 13 | $I->seeInShellOutput('--help'); 14 | $I->seeInShellOutput('--clear-cache'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/cli/_bootstrap.php: -------------------------------------------------------------------------------- 1 | haveModelDefinition( 22 | CompaniesXProducts::class, 23 | [ 24 | 'companyId', 25 | 'productId', 26 | ] 27 | ); 28 | } 29 | 30 | /** 31 | * @param IntegrationTester $I 32 | * 33 | * @return void 34 | */ 35 | public function validateFilters(IntegrationTester $I) 36 | { 37 | $model = new CompaniesXProducts(); 38 | $expected = [ 39 | 'companyId' => Filter::FILTER_ABSINT, 40 | 'productId' => Filter::FILTER_ABSINT, 41 | ]; 42 | $I->assertSame($expected, $model->getModelFilters()); 43 | } 44 | 45 | /** 46 | * @param IntegrationTester $I 47 | * 48 | * @return void 49 | */ 50 | public function validateRelationships(IntegrationTester $I) 51 | { 52 | $actual = $I->getModelRelationships(CompaniesXProducts::class); 53 | $expected = [ 54 | [ 55 | 0, 56 | 'companyId', 57 | Companies::class, 58 | 'id', 59 | ['alias' => Relationships::COMPANIES, 'reusable' => true] 60 | ], 61 | [ 62 | 0, 63 | 'productId', 64 | Products::class, 65 | 'id', 66 | ['alias' => Relationships::PRODUCTS, 'reusable' => true] 67 | ], 68 | ]; 69 | $I->assertSame($expected, $actual); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/integration/library/Models/IndividualTypesCest.php: -------------------------------------------------------------------------------- 1 | haveModelDefinition( 21 | IndividualTypes::class, 22 | [ 23 | 'id', 24 | 'name', 25 | 'description', 26 | ] 27 | ); 28 | } 29 | 30 | /** 31 | * @param IntegrationTester $I 32 | * 33 | * @return void 34 | */ 35 | public function validateFilters(IntegrationTester $I) 36 | { 37 | $model = new IndividualTypes(); 38 | $expected = [ 39 | 'id' => Filter::FILTER_ABSINT, 40 | 'name' => Filter::FILTER_STRING, 41 | 'description' => Filter::FILTER_STRING, 42 | ]; 43 | $I->assertSame($expected, $model->getModelFilters()); 44 | } 45 | 46 | /** 47 | * @param IntegrationTester $I 48 | * 49 | * @return void 50 | */ 51 | public function validateRelationships(IntegrationTester $I) 52 | { 53 | $actual = $I->getModelRelationships(IndividualTypes::class); 54 | $expected = [ 55 | [ 56 | 2, 57 | 'id', 58 | Individuals::class, 59 | 'typeId', 60 | ['alias' => Relationships::INDIVIDUALS, 'reusable' => true] 61 | ], 62 | ]; 63 | $I->assertSame($expected, $actual); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/integration/library/Models/IndividualsCest.php: -------------------------------------------------------------------------------- 1 | haveModelDefinition( 22 | Individuals::class, 23 | [ 24 | 'id', 25 | 'companyId', 26 | 'typeId', 27 | 'prefix', 28 | 'first', 29 | 'middle', 30 | 'last', 31 | 'suffix', 32 | ] 33 | ); 34 | } 35 | 36 | /** 37 | * @param IntegrationTester $I 38 | * 39 | * @return void 40 | */ 41 | public function validateFilters(IntegrationTester $I) 42 | { 43 | $model = new Individuals(); 44 | $expected = [ 45 | 'id' => Filter::FILTER_ABSINT, 46 | 'companyId' => Filter::FILTER_ABSINT, 47 | 'typeId' => Filter::FILTER_ABSINT, 48 | 'prefix' => Filter::FILTER_STRING, 49 | 'first' => Filter::FILTER_STRING, 50 | 'middle' => Filter::FILTER_STRING, 51 | 'last' => Filter::FILTER_STRING, 52 | 'suffix' => Filter::FILTER_STRING, 53 | ]; 54 | $I->assertSame($expected, $model->getModelFilters()); 55 | } 56 | 57 | /** 58 | * @param IntegrationTester $I 59 | * 60 | * @return void 61 | */ 62 | public function validateRelationships(IntegrationTester $I) 63 | { 64 | $actual = $I->getModelRelationships(Individuals::class); 65 | $expected = [ 66 | [ 67 | 0, 68 | 'companyId', 69 | Companies::class, 70 | 'id', 71 | ['alias' => Relationships::COMPANIES, 'reusable' => true] 72 | ], 73 | [ 74 | 1, 75 | 'typeId', 76 | IndividualTypes::class, 77 | 'id', 78 | ['alias' => Relationships::INDIVIDUAL_TYPES, 'reusable' => true] 79 | ], 80 | ]; 81 | $I->assertSame($expected, $actual); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/integration/library/Models/ProductTypesCest.php: -------------------------------------------------------------------------------- 1 | haveModelDefinition( 21 | ProductTypes::class, 22 | [ 23 | 'id', 24 | 'name', 25 | 'description', 26 | ] 27 | ); 28 | } 29 | 30 | /** 31 | * @param IntegrationTester $I 32 | * 33 | * @return void 34 | */ 35 | public function validateFilters(IntegrationTester $I) 36 | { 37 | $model = new ProductTypes(); 38 | $expected = [ 39 | 'id' => Filter::FILTER_ABSINT, 40 | 'name' => Filter::FILTER_STRING, 41 | 'description' => Filter::FILTER_STRING, 42 | ]; 43 | $I->assertSame($expected, $model->getModelFilters()); 44 | } 45 | 46 | /** 47 | * @param IntegrationTester $I 48 | * 49 | * @return void 50 | */ 51 | public function validateRelationships(IntegrationTester $I) 52 | { 53 | $actual = $I->getModelRelationships(ProductTypes::class); 54 | $expected = [ 55 | [ 56 | 2, 57 | 'id', 58 | Products::class, 59 | 'typeId', 60 | ['alias' => Relationships::PRODUCTS, 'reusable' => true] 61 | ], 62 | ]; 63 | $I->assertSame($expected, $actual); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/integration/library/Models/ProductsCest.php: -------------------------------------------------------------------------------- 1 | haveModelDefinition( 22 | Products::class, 23 | [ 24 | 'id', 25 | 'typeId', 26 | 'name', 27 | 'description', 28 | 'quantity', 29 | 'price', 30 | ] 31 | ); 32 | } 33 | 34 | /** 35 | * @param IntegrationTester $I 36 | * 37 | * @return void 38 | */ 39 | public function validateFilters(IntegrationTester $I) 40 | { 41 | $model = new Products(); 42 | $expected = [ 43 | 'id' => Filter::FILTER_ABSINT, 44 | 'typeId' => Filter::FILTER_ABSINT, 45 | 'name' => Filter::FILTER_STRING, 46 | 'description' => Filter::FILTER_STRING, 47 | 'quantity' => Filter::FILTER_ABSINT, 48 | 'price' => Filter::FILTER_FLOAT, 49 | ]; 50 | $I->assertSame($expected, $model->getModelFilters()); 51 | } 52 | 53 | /** 54 | * @param IntegrationTester $I 55 | * 56 | * @return void 57 | */ 58 | public function validateRelationships(IntegrationTester $I) 59 | { 60 | $actual = $I->getModelRelationships(Products::class); 61 | $expected = [ 62 | [ 63 | 0, 64 | 'typeId', 65 | ProductTypes::class, 66 | 'id', 67 | ['alias' => Relationships::PRODUCT_TYPES, 'reusable' => true] 68 | ], 69 | [ 70 | 4, 71 | 'id', 72 | Companies::class, 73 | 'id', 74 | ['alias' => Relationships::COMPANIES, 'reusable' => true] 75 | ], 76 | ]; 77 | 78 | $I->assertSame($expected, $actual); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/integration/library/Models/UsersCest.php: -------------------------------------------------------------------------------- 1 | haveModelDefinition( 28 | Users::class, 29 | [ 30 | 'id', 31 | 'status', 32 | 'username', 33 | 'password', 34 | 'issuer', 35 | 'tokenPassword', 36 | 'tokenId', 37 | ] 38 | ); 39 | } 40 | 41 | /** 42 | * @param IntegrationTester $I 43 | * 44 | * @return void 45 | */ 46 | public function validateFilters(IntegrationTester $I) 47 | { 48 | $model = new Users(); 49 | $expected = [ 50 | 'id' => Filter::FILTER_ABSINT, 51 | 'status' => Filter::FILTER_ABSINT, 52 | 'username' => Filter::FILTER_STRING, 53 | 'password' => Filter::FILTER_STRING, 54 | 'issuer' => Filter::FILTER_STRING, 55 | 'tokenPassword' => Filter::FILTER_STRING, 56 | 'tokenId' => Filter::FILTER_STRING, 57 | ]; 58 | $I->assertSame($expected, $model->getModelFilters()); 59 | } 60 | 61 | /** 62 | * @param IntegrationTester $I 63 | * 64 | * @return void 65 | * @throws ModelException 66 | * @throws ValidatorException 67 | */ 68 | public function checkValidationData(IntegrationTester $I) 69 | { 70 | /** @var Users $user */ 71 | $user = $I->haveRecordWithFields( 72 | Users::class, 73 | [ 74 | 'username' => Data::$testUsername, 75 | 'password' => Data::$testPassword, 76 | 'status' => 1, 77 | 'issuer' => 'https://niden.net', 78 | 'tokenPassword' => Data::$strongPassphrase, 79 | 'tokenId' => Data::$testTokenId, 80 | ] 81 | ); 82 | 83 | $signer = new Hmac(); 84 | $builder = new Builder($signer); 85 | $token = $builder 86 | ->setIssuer('https://niden.net') 87 | ->setAudience($this->getTokenAudience()) 88 | ->setId(Data::$testTokenId) 89 | ->setExpirationTime(time() + 10) 90 | ->setPassphrase(Data::$strongPassphrase) 91 | ->getToken() 92 | ; 93 | 94 | $class = Validator::class; 95 | $actual = $user->getValidationData(); 96 | $I->assertInstanceOf($class, $actual); 97 | } 98 | 99 | /** 100 | * @param IntegrationTester $I 101 | * 102 | * @return void 103 | */ 104 | public function validateRelationships(IntegrationTester $I) 105 | { 106 | $actual = $I->getModelRelationships(Users::class); 107 | $I->assertSame(0, count($actual)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/integration/library/Transformers/BaseTransformerCest.php: -------------------------------------------------------------------------------- 1 | haveRecordWithFields( 21 | Companies::class, 22 | [ 23 | 'name' => 'acme', 24 | 'address' => '123 Phalcon way', 25 | 'city' => 'World', 26 | 'phone' => '555-999-4444', 27 | ] 28 | ); 29 | 30 | $transformer = new BaseTransformer(); 31 | $expected = [ 32 | 'id' => $company->get('id'), 33 | 'name' => $company->get('name'), 34 | 'address' => $company->get('address'), 35 | 'city' => $company->get('city'), 36 | 'phone' => $company->get('phone'), 37 | ]; 38 | 39 | $I->assertSame($expected, $transformer->transform($company)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/integration/library/Validation/CompaniesValidatorCest.php: -------------------------------------------------------------------------------- 1 | '', 18 | 'address' => '123 Phalcon way', 19 | 'city' => 'World', 20 | 'phone' => '555-999-4444', 21 | ]; 22 | $messages = $validation->validate($_POST); 23 | $I->assertSame(1, count($messages)); 24 | $I->assertSame('The company name is required', $messages[0]->getMessage()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/unit.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | # 3 | # Suite for unit or integration tests. 4 | 5 | actor: UnitTester 6 | modules: 7 | enabled: 8 | - Asserts 9 | - Filesystem 10 | - Helper\Unit 11 | -------------------------------------------------------------------------------- /tests/unit/BootstrapCest.php: -------------------------------------------------------------------------------- 1 | assertSame('1.0', $results['jsonapi']['version']); 21 | $I->assertTrue(empty($results['data'])); 22 | $I->assertSame(HttpCode::getDescription(404), $results['errors'][0]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 | setDI($container); 22 | 23 | ob_start(); 24 | $task->mainAction(); 25 | $actual = ob_get_contents(); 26 | ob_end_clean(); 27 | 28 | $year = date('Y'); 29 | $expected = "" 30 | . "******************************************************" . PHP_EOL 31 | . " Phalcon Team | (C) {$year}" . PHP_EOL 32 | . "******************************************************" . PHP_EOL 33 | . "" . PHP_EOL 34 | . "Usage: runCli " . PHP_EOL 35 | . "" . PHP_EOL 36 | . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL 37 | . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL 38 | . PHP_EOL; 39 | 40 | $I->assertSame($expected, $actual); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/unit/cli/BootstrapCest.php: -------------------------------------------------------------------------------- 1 | " . PHP_EOL 27 | . "" . PHP_EOL 28 | . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL 29 | . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL 30 | . PHP_EOL; 31 | 32 | $I->assertSame($expected, $actual); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/cli/ClearCacheCest.php: -------------------------------------------------------------------------------- 1 | register($container); 30 | $cache = new CacheDataProvider(); 31 | $cache->register($container); 32 | $task = new ClearcacheTask(); 33 | $task->setDI($container); 34 | 35 | $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); 36 | $count = iterator_count($iterator); 37 | 38 | $this->createFile(); 39 | $this->createFile(); 40 | $this->createFile(); 41 | $this->createFile(); 42 | 43 | $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); 44 | $I->assertSame($count + 4, iterator_count($iterator)); 45 | 46 | ob_start(); 47 | $task->mainAction(); 48 | $actual = ob_get_contents(); 49 | ob_end_clean(); 50 | 51 | $I->assertGreaterOrEquals(0, strpos($actual, 'Clearing Cache folders')); 52 | $I->assertGreaterOrEquals(0, strpos($actual, 'Cleared Cache folders')); 53 | 54 | $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); 55 | $I->assertSame(1, iterator_count($iterator)); 56 | } 57 | 58 | private function createFile() 59 | { 60 | $name = appPath('/storage/cache/data/') . uniqid('tmp_') . '.cache'; 61 | $pointer = fopen($name, 'wb'); 62 | fwrite($pointer, 'test'); 63 | fclose($pointer); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/unit/config/AutoloaderCest.php: -------------------------------------------------------------------------------- 1 | assertNotEquals(false, $_ENV['APP_DEBUG']); 18 | $I->assertNotEquals(false, $_ENV['APP_ENV']); 19 | $I->assertNotEquals(false, $_ENV['APP_URL']); 20 | $I->assertNotEquals(false, $_ENV['APP_NAME']); 21 | $I->assertNotEquals(false, $_ENV['APP_BASE_URI']); 22 | $I->assertNotEquals(false, $_ENV['APP_SUPPORT_EMAIL']); 23 | $I->assertNotEquals(false, $_ENV['APP_TIMEZONE']); 24 | $I->assertNotEquals(false, $_ENV['CACHE_PREFIX']); 25 | $I->assertNotEquals(false, $_ENV['CACHE_LIFETIME']); 26 | $I->assertNotEquals(false, $_ENV['DATA_API_MYSQL_NAME']); 27 | $I->assertNotEquals(false, $_ENV['LOGGER_DEFAULT_FILENAME']); 28 | $I->assertNotEquals(false, $_ENV['VERSION']); 29 | 30 | $I->assertSame('true', $_ENV['APP_DEBUG']); 31 | $I->assertSame('development', $_ENV['APP_ENV']); 32 | $I->assertSame('http://api.phalcon.ld', $_ENV['APP_URL']); 33 | $I->assertSame('/', $_ENV['APP_BASE_URI']); 34 | $I->assertSame('team@phalcon.io', $_ENV['APP_SUPPORT_EMAIL']); 35 | $I->assertSame('UTC', $_ENV['APP_TIMEZONE']); 36 | $I->assertSame('api_cache_', $_ENV['CACHE_PREFIX']); 37 | $I->assertSame('86400', $_ENV['CACHE_LIFETIME']); 38 | $I->assertSame('api', $_ENV['LOGGER_DEFAULT_FILENAME']); 39 | $I->assertSame('20180401', $_ENV['VERSION']); 40 | } 41 | 42 | public function checkAutoloader(UnitTester $I) 43 | { 44 | require appPath('library/Core/autoload.php'); 45 | 46 | $class = new Response(); 47 | $I->assertTrue($class instanceof Response); 48 | $I->assertTrue(function_exists('Phalcon\Api\Core\envValue')); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/unit/config/ConfigCest.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_array($config)); 17 | $I->assertTrue(isset($config['app'])); 18 | $I->assertTrue(isset($config['cache'])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/config/FunctionsCest.php: -------------------------------------------------------------------------------- 1 | store = $_ENV ?? []; 24 | } 25 | 26 | /** 27 | * @param UnitTester $I 28 | * 29 | * @return void 30 | */ 31 | public function __after(UnitTester $I) 32 | { 33 | $_ENV = $this->store; 34 | } 35 | 36 | /** 37 | * @param UnitTester $I 38 | * 39 | * @return void 40 | */ 41 | public function checkApppath(UnitTester $I) 42 | { 43 | $path = dirname(dirname(dirname(__DIR__))); 44 | $I->assertSame($path, appPath()); 45 | } 46 | 47 | /** 48 | * @param UnitTester $I 49 | * 50 | * @return void 51 | */ 52 | public function checkApppathWithParameter(UnitTester $I) 53 | { 54 | $path = dirname(dirname(dirname(__DIR__))) . '/library/Core/config.php'; 55 | $I->assertSame($path, appPath('library/Core/config.php')); 56 | } 57 | 58 | /** 59 | * @param UnitTester $I 60 | * 61 | * @return void 62 | */ 63 | public function checkEnvvalueAsFalse(UnitTester $I) 64 | { 65 | $_ENV['SOMEVAL'] = false; 66 | $I->assertFalse(envValue('SOMEVAL')); 67 | } 68 | 69 | /** 70 | * @param UnitTester $I 71 | * 72 | * @return void 73 | */ 74 | public function checkEnvvalueAsTrue(UnitTester $I) 75 | { 76 | $_ENV['SOMEVAL'] = true; 77 | $I->assertTrue(envValue('SOMEVAL')); 78 | } 79 | 80 | /** 81 | * @param UnitTester $I 82 | * 83 | * @return void 84 | */ 85 | public function checkEnvvalueWithValue(UnitTester $I) 86 | { 87 | $_ENV['SOMEVAL'] = 'someval'; 88 | $I->assertSame('someval', envValue('SOMEVAL')); 89 | } 90 | 91 | /** 92 | * @param UnitTester $I 93 | * 94 | * @return void 95 | */ 96 | public function checkEnvurlWithUrl(UnitTester $I) 97 | { 98 | $I->assertSame( 99 | 'http://api.phalcon.ld/companies/1', 100 | appUrl(Relationships::COMPANIES, 1) 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/unit/config/ProvidersCest.php: -------------------------------------------------------------------------------- 1 | assertSame(ConfigProvider::class, $providers[0]); 25 | $I->assertSame(LoggerProvider::class, $providers[1]); 26 | $I->assertSame(ErrorHandlerProvider::class, $providers[2]); 27 | $I->assertSame(DatabaseProvider::class, $providers[3]); 28 | $I->assertSame(ModelsMetadataProvider::class, $providers[4]); 29 | $I->assertSame(RequestProvider::class, $providers[5]); 30 | $I->assertSame(ResponseProvider::class, $providers[6]); 31 | $I->assertSame(RouterProvider::class, $providers[7]); 32 | } 33 | 34 | public function checkCliProviders(UnitTester $I) 35 | { 36 | $providers = require(appPath('cli/config/providers.php')); 37 | 38 | $I->assertSame(ConfigProvider::class, $providers[0]); 39 | $I->assertSame(LoggerProvider::class, $providers[1]); 40 | $I->assertSame(ErrorHandlerProvider::class, $providers[2]); 41 | $I->assertSame(DatabaseProvider::class, $providers[3]); 42 | $I->assertSame(ModelsMetadataProvider::class, $providers[4]); 43 | $I->assertSame(CliDispatcherProvider::class, $providers[5]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/unit/library/BootstrapCest.php: -------------------------------------------------------------------------------- 1 | assertTrue($bootstrap->getContainer() instanceof FactoryDefault); 18 | $I->assertTrue($bootstrap->getResponse() instanceof Response); 19 | $I->assertTrue($bootstrap->getApplication() instanceof Micro); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit/library/Constants/FlagsCest.php: -------------------------------------------------------------------------------- 1 | assertSame(1, Flags::ACTIVE); 13 | $I->assertSame(2, Flags::INACTIVE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/unit/library/Constants/RelationshipsCest.php: -------------------------------------------------------------------------------- 1 | assertSame('companies', Relationships::COMPANIES); 13 | $I->assertSame('individual-types', Relationships::INDIVIDUAL_TYPES); 14 | $I->assertSame('individuals', Relationships::INDIVIDUALS); 15 | $I->assertSame('product-types', Relationships::PRODUCT_TYPES); 16 | $I->assertSame('products', Relationships::PRODUCTS); 17 | $I->assertSame('users', Relationships::USERS); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/library/ErrorHandlerCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 21 | $provider = new LoggerProvider(); 22 | $provider->register($diContainer); 23 | 24 | /** @var Config $config */ 25 | $config = $diContainer->getShared('config'); 26 | /** @var Logger $logger */ 27 | $logger = $diContainer->getShared('logger'); 28 | $handler = new ErrorHandler($logger, $config); 29 | 30 | $handler->handle(1, 'test error', 'file.php', 4); 31 | $fileName = appPath('storage/logs/api.log'); 32 | $I->openFile($fileName); 33 | $expected = '[ERROR] [#:1]-[L: 4] : test error (file.php)'; 34 | $I->seeInThisFile($expected); 35 | } 36 | 37 | public function logErrorOnShutdown(UnitTester $I) 38 | { 39 | $diContainer = new FactoryDefault(); 40 | $provider = new ConfigProvider(); 41 | $provider->register($diContainer); 42 | $provider = new LoggerProvider(); 43 | $provider->register($diContainer); 44 | 45 | /** @var Config $config */ 46 | $config = $diContainer->getShared('config'); 47 | /** @var Logger $logger */ 48 | $logger = $diContainer->getShared('logger'); 49 | $handler = new ErrorHandler($logger, $config); 50 | 51 | $handler->shutdown(); 52 | $fileName = appPath('storage/logs/api.log'); 53 | $I->openFile($fileName); 54 | $expected = '[INFO] Shutdown completed'; 55 | $I->seeInThisFile($expected); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/CacheCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 21 | $provider = new CacheDataProvider(); 22 | $provider->register($diContainer); 23 | 24 | $I->assertTrue($diContainer->has('cache')); 25 | /** @var Cache $cache */ 26 | $cache = $diContainer->getShared('cache'); 27 | $I->assertTrue($cache instanceof Cache); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/ConfigCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 20 | 21 | $I->assertTrue($diContainer->has('config')); 22 | $config = $diContainer->getShared('config'); 23 | $I->assertTrue($config instanceof Config); 24 | 25 | $configArray = $config->toArray(); 26 | $I->assertTrue(isset($configArray['app']['version'])); 27 | $I->assertTrue(isset($configArray['app']['timezone'])); 28 | $I->assertTrue(isset($configArray['app']['debug'])); 29 | $I->assertTrue(isset($configArray['app']['env'])); 30 | $I->assertTrue(isset($configArray['app']['devMode'])); 31 | $I->assertTrue(isset($configArray['app']['baseUri'])); 32 | $I->assertTrue(isset($configArray['app']['supportEmail'])); 33 | $I->assertTrue(isset($configArray['app']['time'])); 34 | $I->assertTrue(isset($configArray['cache'])); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/DatabaseCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 21 | $provider = new DatabaseProvider(); 22 | $provider->register($diContainer); 23 | 24 | $I->assertTrue($diContainer->has('db')); 25 | /** @var Mysql $db */ 26 | $db = $diContainer->getShared('db'); 27 | $I->assertTrue($db instanceof Mysql); 28 | $I->assertSame('mysql', $db->getType()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/ErrorHandlerCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 23 | $provider = new LoggerProvider(); 24 | $provider->register($diContainer); 25 | $provider = new ErrorHandlerProvider(); 26 | $provider->register($diContainer); 27 | 28 | $config = $diContainer->getShared('config'); 29 | 30 | $I->assertSame(date_default_timezone_get(), $config->path('app.timezone')); 31 | $I->assertSame(ini_get('display_errors'), 'Off'); 32 | $I->assertSame(E_ALL, (int) ini_get('error_reporting')); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/LoggerCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 21 | $provider = new LoggerProvider(); 22 | $provider->register($diContainer); 23 | 24 | $I->assertTrue($diContainer->has('logger')); 25 | /** @var Logger $logger */ 26 | $logger = $diContainer->getShared('logger'); 27 | $I->assertTrue($logger instanceof Logger); 28 | $I->assertSame('api', $logger->getName()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/ModelsMetadataCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 21 | $provider = new ModelsMetadataProvider(); 22 | $provider->register($diContainer); 23 | 24 | $I->assertTrue($diContainer->has('modelsMetadata')); 25 | /** @var Memory $metadata */ 26 | $metadata = $diContainer->getShared('modelsMetadata'); 27 | $I->assertTrue($metadata instanceof Memory); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/ResponseCest.php: -------------------------------------------------------------------------------- 1 | register($diContainer); 20 | 21 | $I->assertTrue($diContainer->has('response')); 22 | /** @var Response $response */ 23 | $response = $diContainer->getShared('response'); 24 | $I->assertTrue($response instanceof Response); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/unit/library/Providers/RouterCest.php: -------------------------------------------------------------------------------- 1 | setShared('application', $application); 23 | $provider = new ConfigProvider(); 24 | $provider->register($diContainer); 25 | $provider = new RouterProvider(); 26 | $provider->register($diContainer); 27 | 28 | /** @var RouterInterface $router */ 29 | $router = $application->getRouter(); 30 | $routes = $router->getRoutes(); 31 | $expected = [ 32 | ['POST', '/login'], 33 | ['POST', '/companies'], 34 | ['GET', '/users'], 35 | ['GET', '/users/{recordId:[0-9]+}'], 36 | ['GET', '/companies'], 37 | ['GET', '/companies/{recordId:[0-9]+}'], 38 | ['GET', '/companies/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], 39 | ['GET', '/companies/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], 40 | ['GET', '/individuals'], 41 | ['GET', '/individuals/{recordId:[0-9]+}'], 42 | ['GET', '/individuals/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], 43 | ['GET', '/individuals/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], 44 | ['GET', '/individual-types'], 45 | ['GET', '/individual-types/{recordId:[0-9]+}'], 46 | ['GET', '/individual-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], 47 | ['GET', '/individual-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], 48 | ['GET', '/products'], 49 | ['GET', '/products/{recordId:[0-9]+}'], 50 | ['GET', '/products/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], 51 | ['GET', '/products/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], 52 | ['GET', '/product-types'], 53 | ['GET', '/product-types/{recordId:[0-9]+}'], 54 | ['GET', '/product-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], 55 | ['GET', '/product-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], 56 | ]; 57 | 58 | $I->assertSame(24, count($routes)); 59 | foreach ($routes as $index => $route) { 60 | $I->assertSame($expected[$index][0], $route->getHttpMethods()); 61 | $I->assertSame($expected[$index][1], $route->getPattern()); 62 | } 63 | } 64 | } 65 | --------------------------------------------------------------------------------