├── .env.example ├── .gitattributes ├── .github └── workflows │ └── run-tests.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── app ├── Console │ └── Kernel.php ├── Events │ ├── AssetWasCreated.php │ ├── ForgotPasswordRequested.php │ └── PasswordRecovered.php ├── Exceptions │ ├── BodyTooLargeException.php │ ├── EmailNotSentException.php │ ├── Handler.php │ ├── PasswordNotUpdated.php │ └── StoreResourceFailedException.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ ├── Assets │ │ │ │ ├── RenderFileController.php │ │ │ │ └── UploadFileController.php │ │ │ ├── Auth │ │ │ │ ├── PasswordsController.php │ │ │ │ └── RegisterController.php │ │ │ ├── PingController.php │ │ │ └── Users │ │ │ │ ├── PermissionsController.php │ │ │ │ ├── ProfileController.php │ │ │ │ ├── RolesController.php │ │ │ │ └── UsersController.php │ │ └── Controller.php │ ├── Kernel.php │ └── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ └── VerifyCsrfToken.php ├── Models │ ├── Asset.php │ ├── Permission.php │ ├── Role.php │ ├── SocialProvider.php │ └── User.php ├── OAuthGrants │ └── SocialGrant.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Support │ ├── HasPermissionsUuid.php │ ├── HasRolesUuid.php │ ├── HasSocialLogin.php │ └── UuidScopeTrait.php └── Transformers │ ├── Assets │ └── AssetTransformer.php │ └── Users │ ├── PermissionTransformer.php │ ├── RoleTransformer.php │ └── UserTransformer.php ├── artisan ├── bin ├── exec-app-container.sh └── run-scheduler.sh ├── bootstrap ├── app.php ├── autoload.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── database.php ├── filesystems.php ├── fractal.php ├── hashing.php ├── logging.php ├── mail.php ├── permission.php ├── queue.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ ├── PermissionFactory.php │ ├── RoleFactory.php │ ├── SocialProviderFactory.php │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2017_02_09_031936_create_permission_tables.php │ ├── 2017_08_08_193843_create_assets_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ └── 2021_01_10_023024_create_social_providers_table.php └── seeders │ ├── DatabaseSeeder.php │ └── Users │ ├── RoleTableSeeder.php │ └── UsersTableSeeder.php ├── docs └── api │ ├── apiblueprint.apib │ └── blueprint │ ├── apidocs.apib │ ├── dataStructures │ ├── assets.apib │ ├── auth.apib │ ├── errors.apib │ ├── permissions.apib │ ├── roles.apib │ └── users.apib │ └── routes │ ├── assets.apib │ ├── auth.apib │ ├── permissions.apib │ ├── profile.apib │ ├── roles.apib │ └── users.apib ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── mix-manifest.json ├── robots.txt ├── vendor │ └── scribe │ │ ├── css │ │ ├── highlight-atelier-cave-light.css │ │ ├── highlight-darcula.css │ │ ├── highlight-monokai-sublime.css │ │ ├── highlight-monokai.css │ │ ├── highlight-vs2015.css │ │ ├── print.css │ │ └── style.css │ │ ├── images │ │ ├── logo.png │ │ └── navbar.png │ │ └── js │ │ ├── all.js │ │ ├── highlight.pack.js │ │ └── tryitout-2.4.2.js └── web.config ├── readme.md ├── resources ├── docs │ ├── .filemtimes │ ├── authentication.md │ ├── groups │ │ ├── endpoints.md │ │ └── users.md │ └── index.md ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ └── apidocs.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── CreatesApplication.php ├── Feature ├── ApiDocsTest.php ├── Assets │ ├── RenderFileTest.php │ └── UploadImageTest.php ├── Auth │ ├── PasswordsTest.php │ ├── RegisterTest.php │ └── SocialiteTest.php ├── PingTest.php └── Users │ ├── PermissionsEndpointsTest.php │ ├── ProfileEndpointsTest.php │ ├── RolesEndpointsTest.php │ └── UsersEndpointsTest.php ├── Resources ├── bigpic.jpg └── pic.png ├── TestCase.php └── Unit └── Entities └── RoleTest.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | LOG_LEVEL=debug 9 | 10 | DB_CONNECTION=mysql 11 | DB_HOST=127.0.0.1 12 | DB_PORT=3306 13 | DB_DATABASE=laravel 14 | DB_USERNAME=root 15 | DB_PASSWORD= 16 | 17 | BROADCAST_DRIVER=log 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=sync 20 | SESSION_DRIVER=file 21 | SESSION_LIFETIME=120 22 | 23 | MEMCACHED_HOST=127.0.0.1 24 | 25 | REDIS_HOST=127.0.0.1 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_MAILER=smtp 30 | MAIL_HOST=mailhog 31 | MAIL_PORT=1025 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=null 35 | MAIL_FROM_ADDRESS=null 36 | MAIL_FROM_NAME="${APP_NAME}" 37 | 38 | AWS_ACCESS_KEY_ID= 39 | AWS_SECRET_ACCESS_KEY= 40 | AWS_DEFAULT_REGION=us-east-1 41 | AWS_BUCKET= 42 | 43 | PUSHER_APP_ID= 44 | PUSHER_APP_KEY= 45 | PUSHER_APP_SECRET= 46 | PUSHER_APP_CLUSTER=mt1 47 | 48 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 49 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.less linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: "Run Tests - Current" 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: true 10 | matrix: 11 | php: [8.1] 12 | 13 | name: P${{ matrix.php }} 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Cache dependencies 20 | uses: actions/cache@v2 21 | with: 22 | path: ~/.composer/cache/files 23 | key: dependencies-laravel-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php }} 29 | extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv 30 | coverage: none 31 | 32 | - name: Install dependencies 33 | run: | 34 | composer install 35 | php -r "copy('.env.example', '.env');" 36 | php artisan key:generate 37 | php artisan passport:keys 38 | - name: Execute tests 39 | run: vendor/bin/phpunit 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | /vendor 3 | /node_modules 4 | Homestead.yaml 5 | Homestead.json 6 | .env 7 | /public/css/ 8 | /public/js/ 9 | /public/fonts/ 10 | /packages 11 | /storage/oauth-private.key 12 | /storage/oauth-public.key 13 | .idea/ 14 | .DS_Store 15 | .phpunit.result.cache 16 | docker/mysql/ 17 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 8 | $project_path . '/app', 9 | $project_path . '/config', 10 | $project_path . '/database', 11 | $project_path . '/resources', 12 | $project_path . '/routes', 13 | $project_path . '/tests', 14 | ]) 15 | ->name('*.php') 16 | ->notName('*.blade.php') 17 | ->ignoreDotFiles(true) 18 | ->ignoreVCS(true); 19 | 20 | return \ShiftCS\styles($finder); 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jose@ditecnologia.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/joselfonseca/laravel-api). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check and fix the code standard with ``$ composer cs``. 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ composer test 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jose Luis Fonseca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | asset = $asset; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Events/ForgotPasswordRequested.php: -------------------------------------------------------------------------------- 1 | email = $email; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Events/PasswordRecovered.php: -------------------------------------------------------------------------------- 1 | user = $user; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Exceptions/BodyTooLargeException.php: -------------------------------------------------------------------------------- 1 | json([ 56 | 'message' => 'Unauthenticated.', 57 | 'status_code' => 401, 58 | ], 401); 59 | } 60 | if ($exception instanceof BodyTooLargeException) { 61 | return response()->json([ 62 | 'message' => 'The body is too large', 63 | 'status_code' => 413, 64 | ], 413); 65 | } 66 | if ($exception instanceof ValidationException) { 67 | return response()->json([ 68 | 'message' => $exception->getMessage(), 69 | 'status_code' => 422, 70 | 'errors' => $exception->errors(), 71 | ], 422); 72 | } 73 | if ($exception instanceof StoreResourceFailedException) { 74 | return response()->json([ 75 | 'message' => $exception->getMessage(), 76 | 'status_code' => 422, 77 | 'errors' => $exception->errors, 78 | ], 422); 79 | } 80 | if ($exception instanceof NotFoundHttpException) { 81 | return response()->json([ 82 | 'message' => '404 Not Found', 83 | 'status_code' => 404, 84 | ], 404); 85 | } 86 | 87 | return parent::render($request, $exception); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/Exceptions/PasswordNotUpdated.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Assets/RenderFileController.php: -------------------------------------------------------------------------------- 1 | model = $model; 19 | } 20 | 21 | public function show($uuid, Request $request) 22 | { 23 | try { 24 | $model = $this->model->byUuid($uuid)->firstOrFail(); 25 | 26 | return $this->renderFile($model, $request->get('width', null), $request->get('height', null)); 27 | } catch (ModelNotFoundException $e) { 28 | return $this->renderPlaceholder($request->get('width', null), $request->get('height', null)); 29 | } 30 | } 31 | 32 | public function renderFile($model, $width, $height) 33 | { 34 | $image = $this->makeFromPath($width, $height, $model->path); 35 | 36 | return $image->response(); 37 | } 38 | 39 | public function renderPlaceholder($width, $height) 40 | { 41 | $image = Image::cache(function ($image) use ($width, $height) { 42 | $img = $image->canvas(800, 800, '#FFFFFF'); 43 | $this->resize($img, $width, $height); 44 | 45 | return $img; 46 | }, 10, true); 47 | 48 | return $image->response(); 49 | } 50 | 51 | protected function resize($img, $width, $height) 52 | { 53 | if (! empty($width) && ! empty($height)) { 54 | $img->resize($width, $height); 55 | } elseif (! empty($width)) { 56 | $img->resize($width, null, function ($constraint) { 57 | $constraint->aspectRatio(); 58 | }); 59 | } elseif (! empty($height)) { 60 | $img->resize(null, $height, function ($constraint) { 61 | $constraint->aspectRatio(); 62 | }); 63 | } 64 | 65 | return $img; 66 | } 67 | 68 | protected function makeFromPath($width, $height, $path) 69 | { 70 | return Image::cache(function ($image) use ($path, $width, $height) { 71 | $img = $image->make(Storage::get($path)); 72 | $this->resize($img, $width, $height); 73 | 74 | return $img; 75 | }, 10, true); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Assets/UploadFileController.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'type' => 'image', 21 | 'extension' => 'jpeg', 22 | ], 23 | 'image/jpg' => [ 24 | 'type' => 'image', 25 | 'extension' => 'jpg', 26 | ], 27 | 'image/png' => [ 28 | 'type' => 'image', 29 | 'extension' => 'png', 30 | ], 31 | ]; 32 | 33 | protected $client; 34 | 35 | protected $model; 36 | 37 | public function __construct(Client $client, \App\Models\Asset $model) 38 | { 39 | $this->client = $client; 40 | $this->model = $model; 41 | } 42 | 43 | public function store(Request $request) 44 | { 45 | if ($request->isJson()) { 46 | $asset = $this->uploadFromUrl([ 47 | 'url' => $request->get('url'), 48 | 'user' => $request->user(), 49 | ]); 50 | } elseif ($request->hasFile('file')) { 51 | $file = $request->file('file')->getRealPath(); 52 | $asset = $this->uploadFromDirectFile([ 53 | 'mime' => $request->file('file')->getClientMimeType(), 54 | 'content' => file_get_contents($file), 55 | 'Content-Length' => $request->header('Content-Length'), 56 | 'user' => $request->user(), 57 | ]); 58 | } else { 59 | $body = ! (base64_decode($request->getContent())) ? $request->getContent() : base64_decode($request->getContent()); 60 | $asset = $this->uploadFromDirectFile([ 61 | 'mime' => $request->header('Content-Type'), 62 | 'content' => $body, 63 | 'Content-Length' => $request->header('Content-Length'), 64 | 'user' => $request->user(), 65 | ]); 66 | } 67 | 68 | event(new AssetWasCreated($asset)); 69 | 70 | return fractal($asset, new AssetTransformer())->respond(201); 71 | } 72 | 73 | protected function uploadFromDirectFile($attributes = []) 74 | { 75 | $this->validateMime($attributes['mime']); 76 | $this->validateBodySize($attributes['Content-Length'], $attributes['content']); 77 | $path = $this->storeInFileSystem($attributes); 78 | 79 | return $this->storeInDatabase($attributes, $path); 80 | } 81 | 82 | protected function uploadFromUrl($attributes = []) 83 | { 84 | $response = $this->callFileUrl($attributes['url']); 85 | $attributes['mime'] = $response->getHeader('content-type')[0]; 86 | $this->validateMime($attributes['mime']); 87 | $attributes['content'] = $response->getBody(); 88 | $path = $this->storeInFileSystem($attributes); 89 | 90 | return $this->storeInDatabase($attributes, $path); 91 | } 92 | 93 | protected function storeInDatabase(array $attributes, $path) 94 | { 95 | $file = $this->model->create([ 96 | 'type' => $this->validMimes[$attributes['mime']]['type'], 97 | 'path' => $path, 98 | 'mime' => $attributes['mime'], 99 | 'user_id' => ! empty($attributes['user']) ? $attributes['user']->id : null, 100 | ]); 101 | 102 | return $file; 103 | } 104 | 105 | protected function storeInFileSystem(array $attributes) 106 | { 107 | $path = md5(Str::random(16).date('U')).'.'.$this->validMimes[$attributes['mime']]['extension']; 108 | Storage::put($path, $attributes['content']); 109 | 110 | return $path; 111 | } 112 | 113 | protected function callFileUrl($url) 114 | { 115 | try { 116 | return $this->client->get($url); 117 | } catch (TransferException $e) { 118 | throw new StoreResourceFailedException('Validation Error', [ 119 | 'url' => 'The url seems to be unreachable: '.$e->getCode(), 120 | ]); 121 | } 122 | } 123 | 124 | protected function validateMime($mime) 125 | { 126 | if (! array_key_exists($mime, $this->validMimes)) { 127 | throw new StoreResourceFailedException('Validation Error', [ 128 | 'Content-Type' => 'The Content Type sent is not valid', 129 | ]); 130 | } 131 | } 132 | 133 | protected function validateBodySize($contentLength, $content) 134 | { 135 | if ($contentLength > config('files.maxsize', 1000000) || mb_strlen($content) > config('files.maxsize', 1000000)) { 136 | throw new BodyTooLargeException(); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Auth/PasswordsController.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 20 | 'email' => 'required|email|exists:users,email', 21 | ]); 22 | 23 | $response = $this->broker()->sendResetLink(['email' => $request->get('email')]); 24 | 25 | if ($response == Password::RESET_LINK_SENT) { 26 | event(new ForgotPasswordRequested($request->get('email'))); 27 | 28 | return response()->json(null, 201); 29 | } 30 | 31 | throw new EmailNotSentException(__($response)); 32 | } 33 | 34 | public function update(Request $request) 35 | { 36 | $this->validate($request, [ 37 | 'email' => 'required|email|exists:users,email', 38 | 'token' => 'required|exists:password_resets,token', 39 | 'password' => 'required|min:8', 40 | ]); 41 | 42 | $response = $this->broker()->reset($request->except('_token'), function ($user, $password) { 43 | $user->password = Hash::make($password); 44 | $user->save(); 45 | }); 46 | 47 | if ($response == Password::PASSWORD_RESET) { 48 | event(new PasswordRecovered(User::where('email', $request->get('email'))->first())); 49 | 50 | return response()->json([ 51 | 'message' => __($response), 52 | ]); 53 | } 54 | 55 | throw new PasswordNotUpdated(__($response)); 56 | } 57 | 58 | public function broker() 59 | { 60 | return Password::broker(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | model = $model; 18 | } 19 | 20 | public function store(Request $request) 21 | { 22 | $this->validate($request, [ 23 | 'name' => 'required', 24 | 'email' => 'required|email|unique:users,email', 25 | 'password' => 'required|min:8|confirmed', 26 | ]); 27 | $user = $this->model->create($request->all()); 28 | $user->assignRole('User'); 29 | event(new Registered($user)); 30 | 31 | return fractal($user, new UserTransformer())->respond(201); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/PingController.php: -------------------------------------------------------------------------------- 1 | json([ 13 | 'status' => 'ok', 14 | 'timestamp' => Carbon::now(), 15 | 'host' => request()->ip(), 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Users/PermissionsController.php: -------------------------------------------------------------------------------- 1 | model = $model; 17 | $this->middleware('permission:List permissions')->only('index'); 18 | } 19 | 20 | public function index(Request $request) 21 | { 22 | $paginator = $this->model->paginate($request->get('limit', config('app.pagination_limit'))); 23 | if ($request->has('limit')) { 24 | $paginator->appends('limit', $request->get('limit')); 25 | } 26 | 27 | return fractal($paginator, new PermissionTransformer())->respond(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Users/ProfileController.php: -------------------------------------------------------------------------------- 1 | respond(); 17 | } 18 | 19 | public function update(Request $request) 20 | { 21 | $user = Auth::user(); 22 | $rules = [ 23 | 'name' => 'required', 24 | 'email' => 'required|email|unique:users,email,'.$user->id, 25 | ]; 26 | if ($request->method() == 'PATCH') { 27 | $rules = [ 28 | 'name' => 'sometimes|required', 29 | 'email' => 'sometimes|required|email|unique:users,email,'.$user->id, 30 | ]; 31 | } 32 | $this->validate($request, $rules); 33 | // Except password as we don't want to let the users change a password from this endpoint 34 | $user->update($request->except('_token', 'password')); 35 | 36 | return fractal($user->fresh(), new UserTransformer())->respond(); 37 | } 38 | 39 | public function updatePassword(Request $request) 40 | { 41 | $user = Auth::user(); 42 | $this->validate($request, [ 43 | 'current_password' => 'required', 44 | 'password' => 'required|min:8|confirmed', 45 | ]); 46 | // verify the old password given is valid 47 | if (! app(Hasher::class)->check($request->get('current_password'), $user->password)) { 48 | throw new StoreResourceFailedException('Validation Issue', [ 49 | 'old_password' => 'The current password is incorrect', 50 | ]); 51 | } 52 | $user->password = bcrypt($request->get('password')); 53 | $user->save(); 54 | 55 | return fractal($user->fresh(), new UserTransformer())->respond(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Users/RolesController.php: -------------------------------------------------------------------------------- 1 | model = $model; 17 | $this->middleware('permission:List roles')->only('index'); 18 | $this->middleware('permission:List roles')->only('show'); 19 | $this->middleware('permission:Create roles')->only('store'); 20 | $this->middleware('permission:Update roles')->only('update'); 21 | $this->middleware('permission:Delete roles')->only('destroy'); 22 | } 23 | 24 | public function index(Request $request) 25 | { 26 | $paginator = $this->model->with('permissions')->paginate($request->get('limit', config('app.pagination_limit'))); 27 | if ($request->has('limit')) { 28 | $paginator->appends('limit', $request->get('limit')); 29 | } 30 | 31 | return fractal($paginator, new RoleTransformer())->respond(); 32 | } 33 | 34 | public function show($id) 35 | { 36 | $role = $this->model->with('permissions')->byUuid($id)->firstOrFail(); 37 | 38 | return fractal($role, new RoleTransformer())->respond(); 39 | } 40 | 41 | public function store(Request $request) 42 | { 43 | $this->validate($request, [ 44 | 'name' => 'required', 45 | ]); 46 | $role = $this->model->create($request->all()); 47 | if ($request->has('permissions')) { 48 | $role->syncPermissions($request['permissions']); 49 | } 50 | 51 | return fractal($role, new RoleTransformer())->respond(201); 52 | } 53 | 54 | public function update(Request $request, $uuid) 55 | { 56 | $role = $this->model->byUuid($uuid)->firstOrFail(); 57 | $this->validate($request, [ 58 | 'name' => 'required', 59 | ]); 60 | $role->update($request->except('_token')); 61 | if ($request->has('permissions')) { 62 | $role->syncPermissions($request['permissions']); 63 | } 64 | 65 | return fractal($role->fresh(), new RoleTransformer())->respond(); 66 | } 67 | 68 | public function destroy(Request $request, $uuid) 69 | { 70 | $role = $this->model->byUuid($uuid)->firstOrFail(); 71 | $role->delete(); 72 | 73 | return response()->json(null, 204); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Users/UsersController.php: -------------------------------------------------------------------------------- 1 | model = $model; 17 | $this->middleware('permission:List users')->only('index'); 18 | $this->middleware('permission:List users')->only('show'); 19 | $this->middleware('permission:Create users')->only('store'); 20 | $this->middleware('permission:Update users')->only('update'); 21 | $this->middleware('permission:Delete users')->only('destroy'); 22 | } 23 | 24 | public function index(Request $request) 25 | { 26 | $paginator = $this->model->with('roles.permissions')->paginate($request->get('limit', config('app.pagination_limit', 20))); 27 | if ($request->has('limit')) { 28 | $paginator->appends('limit', $request->get('limit')); 29 | } 30 | 31 | return fractal($paginator, new UserTransformer())->respond(); 32 | } 33 | 34 | public function show($id) 35 | { 36 | $user = $this->model->with('roles.permissions')->byUuid($id)->firstOrFail(); 37 | 38 | return fractal($user, new UserTransformer())->respond(); 39 | } 40 | 41 | public function store(Request $request) 42 | { 43 | $this->validate($request, [ 44 | 'name' => 'required', 45 | 'email' => 'required|email|unique:users,email', 46 | 'password' => 'required|min:8|confirmed', 47 | ]); 48 | $user = $this->model->create($request->all()); 49 | if ($request->has('roles')) { 50 | $user->syncRoles($request['roles']); 51 | } 52 | 53 | return fractal($user, new UserTransformer())->respond(201); 54 | } 55 | 56 | public function update(Request $request, $uuid) 57 | { 58 | $user = $this->model->byUuid($uuid)->firstOrFail(); 59 | $rules = [ 60 | 'name' => 'required', 61 | 'email' => 'required|email|unique:users,email,'.$user->id, 62 | ]; 63 | if ($request->method() == 'PATCH') { 64 | $rules = [ 65 | 'name' => 'sometimes|required', 66 | 'email' => 'sometimes|required|email|unique:users,email,'.$user->id, 67 | ]; 68 | } 69 | $this->validate($request, $rules); 70 | // Except password as we don't want to let the users change a password from this endpoint 71 | $user->update($request->except('_token', 'password')); 72 | if ($request->has('roles')) { 73 | $user->syncRoles($request['roles']); 74 | } 75 | 76 | return fractal($user->fresh(), new UserTransformer())->respond(); 77 | } 78 | 79 | public function destroy(Request $request, $uuid) 80 | { 81 | $user = $this->model->byUuid($uuid)->firstOrFail(); 82 | $user->delete(); 83 | 84 | return response()->json(null, 204); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | [ 33 | \App\Http\Middleware\EncryptCookies::class, 34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 35 | \Illuminate\Session\Middleware\StartSession::class, 36 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 38 | \App\Http\Middleware\VerifyCsrfToken::class, 39 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 40 | ], 41 | 42 | 'api' => [ 43 | 'throttle:api', 44 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 45 | ], 46 | ]; 47 | 48 | /** 49 | * The application's route middleware. 50 | * 51 | * These middleware may be assigned to groups or used individually. 52 | * 53 | * @var array 54 | */ 55 | protected $routeMiddleware = [ 56 | 'auth' => \App\Http\Middleware\Authenticate::class, 57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 58 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 59 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 60 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 61 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 62 | 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, 63 | 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, 64 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 65 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 66 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 67 | ]; 68 | } 69 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | |string|null 14 | */ 15 | protected $proxies; 16 | 17 | /** 18 | * The headers that should be used to detect proxies. 19 | * 20 | * @var int 21 | */ 22 | protected $headers = 23 | Request::HEADER_X_FORWARDED_FOR | 24 | Request::HEADER_X_FORWARDED_HOST | 25 | Request::HEADER_X_FORWARDED_PORT | 26 | Request::HEADER_X_FORWARDED_PROTO | 27 | Request::HEADER_X_FORWARDED_AWS_ELB; 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | hasMany(SocialProvider::class); 59 | } 60 | 61 | public static function create(array $attributes = []) 62 | { 63 | if (array_key_exists('password', $attributes)) { 64 | $attributes['password'] = Hash::make($attributes['password']); 65 | } 66 | 67 | $model = static::query()->create($attributes); 68 | 69 | return $model; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/OAuthGrants/SocialGrant.php: -------------------------------------------------------------------------------- 1 | setUserRepository($userRepository); 22 | $this->setRefreshTokenRepository($refreshTokenRepository); 23 | $this->refreshTokenTTL = new DateInterval('P1M'); 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL) 30 | { 31 | // Validate request 32 | $client = $this->validateClient($request); 33 | $scopes = $this->validateScopes($this->getRequestParameter('scope', $request)); 34 | $user = $this->validateUser($request); 35 | // Finalize the requested scopes 36 | $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier()); 37 | // Issue and persist new tokens 38 | $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $scopes); 39 | $refreshToken = $this->issueRefreshToken($accessToken); 40 | // Inject tokens into response 41 | $responseType->setAccessToken($accessToken); 42 | $responseType->setRefreshToken($refreshToken); 43 | 44 | return $responseType; 45 | } 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public function getIdentifier() 51 | { 52 | return 'social_grant'; 53 | } 54 | 55 | /** 56 | * @param ServerRequestInterface $request 57 | * @return UserEntityInterface 58 | * 59 | * @throws OAuthServerException 60 | */ 61 | protected function validateUser(ServerRequestInterface $request) 62 | { 63 | $laravelRequest = new Request($request->getParsedBody()); 64 | $user = $this->getUserEntityByRequest($laravelRequest); 65 | if (false === $user instanceof UserEntityInterface) { 66 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); 67 | 68 | throw OAuthServerException::invalidCredentials(); 69 | } 70 | 71 | return $user; 72 | } 73 | 74 | /** 75 | * Retrieve user by request. 76 | * 77 | * @param \Illuminate\Http\Request $request 78 | * @return null|\Laravel\Passport\Bridge\User 79 | * 80 | * @throws \League\OAuth2\Server\Exception\OAuthServerException 81 | */ 82 | protected function getUserEntityByRequest(Request $request) 83 | { 84 | if (is_null($model = config('auth.providers.users.model'))) { 85 | throw OAuthServerException::serverError('Unable to determine user model from configuration.'); 86 | } 87 | if (method_exists($model, 'byOAuthToken')) { 88 | $user = (new $model())->byOAuthToken($request); 89 | } else { 90 | throw OAuthServerException::serverError('Unable to find byLoggedInUser method on user model.'); 91 | } 92 | 93 | return ($user) ? new User($user->id) : null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerPolicies(); 32 | $this->extendAuthorizationServer(); 33 | Passport::routes(); 34 | Passport::tokensExpireIn(Carbon::now()->addHours(24)); 35 | Passport::refreshTokensExpireIn(Carbon::now()->addDays(60)); 36 | } 37 | 38 | protected function makeSocialRequestGrant() 39 | { 40 | $grant = new SocialGrant( 41 | $this->app->make(UserRepository::class), 42 | $this->app->make(RefreshTokenRepository::class) 43 | ); 44 | $grant->setRefreshTokenTTL(Passport::refreshTokensExpireIn()); 45 | 46 | return $grant; 47 | } 48 | 49 | protected function extendAuthorizationServer() 50 | { 51 | $this->app->extend(AuthorizationServer::class, function ($server) { 52 | return tap($server, function ($server) { 53 | $server->enableGrantType( 54 | $this->makeSocialRequestGrant(), 55 | Passport::tokensExpireIn() 56 | ); 57 | }); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | parent::boot(); 31 | // 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 39 | 40 | $this->routes(function () { 41 | Route::prefix('api') 42 | ->middleware('api') 43 | ->namespace($this->namespace) 44 | ->group(base_path('routes/api.php')); 45 | 46 | Route::middleware('web') 47 | ->namespace($this->namespace) 48 | ->group(base_path('routes/web.php')); 49 | }); 50 | } 51 | 52 | /** 53 | * Configure the rate limiters for the application. 54 | * 55 | * @return void 56 | */ 57 | protected function configureRateLimiting() 58 | { 59 | RateLimiter::for('api', function (Request $request) { 60 | return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/Support/HasPermissionsUuid.php: -------------------------------------------------------------------------------- 1 | where('name', $permissions)->orWhere('uuid', $permissions)->first(); 23 | } 24 | 25 | if (is_array($permissions)) { 26 | return app(PermissionEntity::class)->whereIn('name', $permissions)->orWhereIn('uuid', $permissions)->get(); 27 | } 28 | 29 | return $permissions; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Support/HasRolesUuid.php: -------------------------------------------------------------------------------- 1 | where('name', $role)->orWhere('uuid', $role)->first(); 18 | } 19 | 20 | return $role; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Support/HasSocialLogin.php: -------------------------------------------------------------------------------- 1 | get('provider'))->userFromToken($request->get('token')); 17 | 18 | try { 19 | $user = static::whereHas('socialProviders', function ($query) use ($request, $userData) { 20 | $query->where('provider', Str::lower($request->get('provider')))->where('provider_id', $userData->getId()); 21 | })->firstOrFail(); 22 | } catch (ModelNotFoundException $e) { 23 | $user = static::where('email', $userData->getEmail())->first(); 24 | if (! $user) { 25 | $user = static::create([ 26 | 'name' => $userData->getName(), 27 | 'email' => $userData->getEmail(), 28 | 'uuid' => Str::uuid(), 29 | 'password' => Hash::make(Str::random(16)), 30 | 'email_verified_at' => now(), 31 | ]); 32 | } 33 | SocialProvider::create([ 34 | 'user_id' => $user->id, 35 | 'provider' => $request->get('provider'), 36 | 'provider_id' => $userData->getId(), 37 | ]); 38 | } 39 | 40 | return $user; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Support/UuidScopeTrait.php: -------------------------------------------------------------------------------- 1 | where('uuid', $uuid); 20 | } 21 | 22 | /** 23 | * Boot the uuid scope trait for a model. 24 | * 25 | * @return void 26 | */ 27 | protected static function bootUuidScopeTrait() 28 | { 29 | static::creating(function ($model) { 30 | if (empty($model->uuid)) { 31 | $model->uuid = (string) Str::uuid(); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Transformers/Assets/AssetTransformer.php: -------------------------------------------------------------------------------- 1 | $model->uuid, 20 | 'type' => $model->type, 21 | 'path' => $model->path, 22 | 'mime' => $model->mime, 23 | 'links' => [ 24 | 'full' => url('api/assets/'.$model->uuid.'/render'), 25 | 'thumb' => url('api/assets/'.$model->uuid.'/render?width=200&height=200'), 26 | ], 27 | 'created_at' => $model->created_at->toIso8601String(), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Transformers/Users/PermissionTransformer.php: -------------------------------------------------------------------------------- 1 | $model->uuid, 21 | 'name' => $model->name, 22 | 'created_at' => $model->created_at->toIso8601String(), 23 | 'updated_at' => $model->updated_at->toIso8601String(), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Transformers/Users/RoleTransformer.php: -------------------------------------------------------------------------------- 1 | $model->uuid, 26 | 'name' => $model->name, 27 | 'created_at' => $model->created_at->toIso8601String(), 28 | 'updated_at' => $model->updated_at->toIso8601String(), 29 | ]; 30 | } 31 | 32 | /** 33 | * @param Role $model 34 | * @return \League\Fractal\Resource\Collection 35 | */ 36 | public function includePermissions(Role $model) 37 | { 38 | return $this->collection($model->permissions, new PermissionTransformer()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Transformers/Users/UserTransformer.php: -------------------------------------------------------------------------------- 1 | $model->uuid, 26 | 'name' => $model->name, 27 | 'email' => $model->email, 28 | 'created_at' => $model->created_at->toIso8601String(), 29 | 'updated_at' => $model->updated_at->toIso8601String(), 30 | ]; 31 | } 32 | 33 | /** 34 | * @param \App\Model\User $model 35 | * @return \League\Fractal\Resource\Collection 36 | */ 37 | public function includeRoles(User $model) 38 | { 39 | return $this->collection($model->roles, new RoleTransformer()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 32 | 33 | $status = $kernel->handle( 34 | $input = new Symfony\Component\Console\Input\ArgvInput, 35 | new Symfony\Component\Console\Output\ConsoleOutput 36 | ); 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Shutdown The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once Artisan has finished running. We will fire off the shutdown events 44 | | so that any final work may be done by the application before we shut 45 | | down the process. This is the last thing to happen to the request. 46 | | 47 | */ 48 | 49 | $kernel->terminate($input, $status); 50 | 51 | exit($status); 52 | -------------------------------------------------------------------------------- /bin/exec-app-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x #echo on 4 | 5 | docker-compose -f docker-compose.yml exec app /bin/bash -------------------------------------------------------------------------------- /bin/run-scheduler.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while [ true ] 4 | do 5 | php /var/www/html/artisan schedule:run --quiet --no-interaction & 6 | sleep 60 7 | done 8 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'api', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'api' => [ 40 | 'driver' => 'passport', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'web' => [ 45 | 'driver' => 'session', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication drivers have a user provider. This defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | mechanisms used by this application to persist your user's data. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | sources which represent each model / table. These sources may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => \App\Models\User::class, 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | You may specify multiple password reset configurations if you have more 85 | | than one user table or model in the application and you want to have 86 | | separate password reset settings based on the specific user types. 87 | | 88 | | The expire time is the number of minutes that the reset token should be 89 | | considered valid. This security feature keeps tokens short-lived so 90 | | they have less time to be guessed. You may change this as needed. 91 | | 92 | */ 93 | 94 | 'passwords' => [ 95 | 'users' => [ 96 | 'provider' => 'users', 97 | 'table' => 'password_resets', 98 | 'expire' => 60, 99 | 'throttle' => 60, 100 | ], 101 | ], 102 | 103 | /* 104 | |-------------------------------------------------------------------------- 105 | | Password Confirmation Timeout 106 | |-------------------------------------------------------------------------- 107 | | 108 | | Here you may define the amount of seconds before a password confirmation 109 | | times out and the user is prompted to re-enter their password via the 110 | | confirmation screen. By default, the timeout lasts for three hours. 111 | | 112 | */ 113 | 'password_timeout' => 10800, 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Broadcast Connections 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Here you may define all of the broadcast connections that will be used 23 | | to broadcast events to other systems or over websockets. Samples of 24 | | each available type of connection are provided inside this array. 25 | | 26 | */ 27 | 'connections' => [ 28 | 'pusher' => [ 29 | 'driver' => 'pusher', 30 | 'key' => env('PUSHER_APP_KEY'), 31 | 'secret' => env('PUSHER_APP_SECRET'), 32 | 'app_id' => env('PUSHER_APP_ID'), 33 | 'options' => [ 34 | 'cluster' => env('PUSHER_APP_CLUSTER'), 35 | 'encrypted' => true, 36 | ], 37 | ], 38 | 'redis' => [ 39 | 'driver' => 'redis', 40 | 'connection' => 'default', 41 | ], 42 | 'log' => [ 43 | 'driver' => 'log', 44 | ], 45 | 'null' => [ 46 | 'driver' => 'null', 47 | ], 48 | ], 49 | ]; 50 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | */ 30 | 'stores' => [ 31 | 'apc' => [ 32 | 'driver' => 'apc', 33 | ], 34 | 'array' => [ 35 | 'driver' => 'array', 36 | ], 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'cache', 40 | 'connection' => null, 41 | ], 42 | 'file' => [ 43 | 'driver' => 'file', 44 | 'path' => storage_path('framework/cache/data'), 45 | ], 46 | 'memcached' => [ 47 | 'driver' => 'memcached', 48 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 49 | 'sasl' => [ 50 | env('MEMCACHED_USERNAME'), 51 | env('MEMCACHED_PASSWORD'), 52 | ], 53 | 'options' => [ 54 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 55 | ], 56 | 'servers' => [ 57 | [ 58 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 59 | 'port' => env('MEMCACHED_PORT', 11211), 60 | 'weight' => 100, 61 | ], 62 | ], 63 | ], 64 | 'redis' => [ 65 | 'driver' => 'redis', 66 | 'connection' => 'cache', 67 | ], 68 | 'dynamodb' => [ 69 | 'driver' => 'dynamodb', 70 | 'key' => env('AWS_ACCESS_KEY_ID'), 71 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 72 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 73 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 74 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 75 | ], 76 | ], 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Cache Key Prefix 80 | |-------------------------------------------------------------------------- 81 | | 82 | | When utilizing a RAM based store such as APC or Memcached, there might 83 | | be other applications utilizing the same cache. So, we'll specify a 84 | | value to get prefixed to all our keys so we can avoid collisions. 85 | | 86 | */ 87 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), 88 | ]; 89 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'mysql'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Database Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here are each of the database connections setup for your application. 26 | | Of course, examples of configuring each database platform that is 27 | | supported by Laravel is shown below to make development simple. 28 | | 29 | | 30 | | All database work in Laravel is done through the PHP PDO facilities 31 | | so make sure you have the driver for your particular database of 32 | | choice installed on your machine before you begin development. 33 | | 34 | */ 35 | 36 | 'connections' => [ 37 | 38 | 'sqlite' => [ 39 | 'driver' => 'sqlite', 40 | 'url' => env('DATABASE_URL'), 41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 42 | 'prefix' => '', 43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 44 | ], 45 | 46 | 'mysql' => [ 47 | 'driver' => 'mysql', 48 | 'url' => env('DATABASE_URL'), 49 | 'host' => env('DB_HOST', '127.0.0.1'), 50 | 'port' => env('DB_PORT', '3306'), 51 | 'database' => env('DB_DATABASE', 'forge'), 52 | 'username' => env('DB_USERNAME', 'forge'), 53 | 'password' => env('DB_PASSWORD', ''), 54 | 'unix_socket' => env('DB_SOCKET', ''), 55 | 'charset' => 'utf8mb4', 56 | 'collation' => 'utf8mb4_unicode_ci', 57 | 'prefix' => '', 58 | 'prefix_indexes' => true, 59 | 'strict' => true, 60 | 'engine' => null, 61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 63 | ]) : [], 64 | ], 65 | 66 | 'pgsql' => [ 67 | 'driver' => 'pgsql', 68 | 'url' => env('DATABASE_URL'), 69 | 'host' => env('DB_HOST', '127.0.0.1'), 70 | 'port' => env('DB_PORT', '5432'), 71 | 'database' => env('DB_DATABASE', 'forge'), 72 | 'username' => env('DB_USERNAME', 'forge'), 73 | 'password' => env('DB_PASSWORD', ''), 74 | 'charset' => 'utf8', 75 | 'prefix' => '', 76 | 'prefix_indexes' => true, 77 | 'schema' => 'public', 78 | 'sslmode' => 'prefer', 79 | ], 80 | 'sqlsrv' => [ 81 | 'driver' => 'sqlsrv', 82 | 'url' => env('DATABASE_URL'), 83 | 'host' => env('DB_HOST', 'localhost'), 84 | 'port' => env('DB_PORT', '1433'), 85 | 'database' => env('DB_DATABASE', 'forge'), 86 | 'username' => env('DB_USERNAME', 'forge'), 87 | 'password' => env('DB_PASSWORD', ''), 88 | 'charset' => 'utf8', 89 | 'prefix' => '', 90 | 'prefix_indexes' => true, 91 | ], 92 | ], 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Migration Repository Table 96 | |-------------------------------------------------------------------------- 97 | | 98 | | This table keeps track of all the migrations that have already run for 99 | | your application. Using this information, we can determine which of 100 | | the migrations on disk haven't actually been run in the database. 101 | | 102 | */ 103 | 'migrations' => 'migrations', 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Redis Databases 107 | |-------------------------------------------------------------------------- 108 | | 109 | | Redis is an open source, fast, and advanced key-value store that also 110 | | provides a richer body of commands than a typical key-value system 111 | | such as APC or Memcached. Laravel makes it easy to dig right in. 112 | | 113 | */ 114 | 'redis' => [ 115 | 'client' => env('REDIS_CLIENT', 'phpredis'), 116 | 'options' => [ 117 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 118 | 'prefix' => Str::slug(env('APP_NAME', 'laravel'), '_').'_database_', 119 | ], 120 | 'default' => [ 121 | 'url' => env('REDIS_URL'), 122 | 'host' => env('REDIS_HOST', '127.0.0.1'), 123 | 'password' => env('REDIS_PASSWORD', null), 124 | 'port' => env('REDIS_PORT', 6379), 125 | 'database' => env('REDIS_DB', 0), 126 | ], 127 | 'cache' => [ 128 | 'url' => env('REDIS_URL'), 129 | 'host' => env('REDIS_HOST', '127.0.0.1'), 130 | 'password' => env('REDIS_PASSWORD', null), 131 | 'port' => env('REDIS_PORT', 6379), 132 | 'database' => env('REDIS_CACHE_DB', 1), 133 | ], 134 | ], 135 | ]; 136 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | Default Cloud Filesystem Disk 18 | |-------------------------------------------------------------------------- 19 | | 20 | | Many applications store files both locally and in the cloud. For this 21 | | reason, you may specify a default "cloud" driver here. This driver 22 | | will be bound as the Cloud disk implementation in the container. 23 | | 24 | */ 25 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Filesystem Disks 29 | |-------------------------------------------------------------------------- 30 | | 31 | | Here you may configure as many filesystem "disks" as you wish, and you 32 | | may even configure multiple disks of the same driver. Defaults have 33 | | been setup for each driver as an example of the required options. 34 | | 35 | | Supported Drivers: "local", "ftp", "sftp", "s3" 36 | | 37 | */ 38 | 'disks' => [ 39 | 'local' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app'), 42 | ], 43 | 'public' => [ 44 | 'driver' => 'local', 45 | 'root' => storage_path('app/public'), 46 | 'url' => env('APP_URL').'/storage', 47 | 'visibility' => 'public', 48 | ], 49 | 's3' => [ 50 | 'driver' => 's3', 51 | 'key' => env('AWS_ACCESS_KEY_ID'), 52 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 53 | 'region' => env('AWS_DEFAULT_REGION'), 54 | 'bucket' => env('AWS_BUCKET'), 55 | 'url' => env('AWS_URL'), 56 | ], 57 | ], 58 | ]; 59 | -------------------------------------------------------------------------------- /config/fractal.php: -------------------------------------------------------------------------------- 1 | '', 10 | 11 | /* The default paginator to be used when performing a transformation. It 12 | * may be left empty to use Fractal's default one. This can either be a 13 | * string or a League\Fractal\Paginator\PaginatorInterface subclass. 14 | */ 15 | 'default_paginator' => '', 16 | 17 | /* 18 | * League\Fractal\Serializer\JsonApiSerializer will use this value 19 | * as a prefix for generated links. Set to `null` to disable this. 20 | */ 21 | 'base_url' => null, 22 | 23 | /* 24 | * If you wish to override or extend the default Spatie\Fractal\Fractal 25 | * instance provide the name of the class you want to use. 26 | */ 27 | 'fractal_class' => Spatie\Fractal\Fractal::class, 28 | 29 | 'auto_includes' => [ 30 | 31 | /* 32 | * If enabled Fractal will automatically add the includes who's 33 | * names are present in the `include` request parameter. 34 | */ 35 | 'enabled' => true, 36 | 37 | /* 38 | * The name of key in the request to where we should look for the includes to include. 39 | */ 40 | 'request_key' => 'include', 41 | ], 42 | ]; 43 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Bcrypt Options 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Here you may specify the configuration options that should be used when 23 | | passwords are hashed using the Bcrypt algorithm. This will allow you 24 | | to control the amount of time it takes to hash the given password. 25 | | 26 | */ 27 | 'bcrypt' => [ 28 | 'rounds' => env('BCRYPT_ROUNDS', 10), 29 | ], 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Argon Options 33 | |-------------------------------------------------------------------------- 34 | | 35 | | Here you may specify the configuration options that should be used when 36 | | passwords are hashed using the Argon algorithm. These will allow you 37 | | to control the amount of time it takes to hash the given password. 38 | | 39 | */ 40 | 'argon' => [ 41 | 'memory' => 1024, 42 | 'threads' => 2, 43 | 'time' => 2, 44 | ], 45 | ]; 46 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Log Channels 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Here you may configure the log channels for your application. Out of 28 | | the box, Laravel uses the Monolog PHP logging library. This gives 29 | | you a variety of powerful log handlers / formatters to utilize. 30 | | 31 | | Available Drivers: "single", "daily", "slack", "syslog", 32 | | "errorlog", "monolog", 33 | | "custom", "stack" 34 | | 35 | */ 36 | 37 | 'channels' => [ 38 | 'stack' => [ 39 | 'driver' => 'stack', 40 | 'channels' => ['daily'], 41 | 'ignore_exceptions' => false, 42 | ], 43 | 44 | 'single' => [ 45 | 'driver' => 'single', 46 | 'path' => storage_path('logs/laravel.log'), 47 | 'level' => 'debug', 48 | ], 49 | 50 | 'daily' => [ 51 | 'driver' => 'daily', 52 | 'path' => storage_path('logs/laravel.log'), 53 | 'level' => 'debug', 54 | 'days' => 14, 55 | ], 56 | 57 | 'slack' => [ 58 | 'driver' => 'slack', 59 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 60 | 'username' => 'Laravel Log', 61 | 'emoji' => ':boom:', 62 | 'level' => 'critical', 63 | ], 64 | 65 | 'papertrail' => [ 66 | 'driver' => 'monolog', 67 | 'level' => 'debug', 68 | 'handler' => SyslogUdpHandler::class, 69 | 'handler_with' => [ 70 | 'host' => env('PAPERTRAIL_URL'), 71 | 'port' => env('PAPERTRAIL_PORT'), 72 | ], 73 | ], 74 | 75 | 'stderr' => [ 76 | 'driver' => 'monolog', 77 | 'handler' => StreamHandler::class, 78 | 'formatter' => env('LOG_STDERR_FORMATTER'), 79 | 'with' => [ 80 | 'stream' => 'php://stderr', 81 | ], 82 | ], 83 | 84 | 'syslog' => [ 85 | 'driver' => 'syslog', 86 | 'level' => 'debug', 87 | ], 88 | 89 | 'errorlog' => [ 90 | 'driver' => 'errorlog', 91 | 'level' => 'debug', 92 | ], 93 | 94 | 'null' => [ 95 | 'driver' => 'monolog', 96 | 'handler' => NullHandler::class, 97 | ], 98 | ], 99 | 100 | ]; 101 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | SMTP Host Address 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may provide the host address of the SMTP server used by your 24 | | applications. A default option is provided that is compatible with 25 | | the Mailgun mail service which will provide reliable deliveries. 26 | | 27 | */ 28 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | SMTP Host Port 32 | |-------------------------------------------------------------------------- 33 | | 34 | | This is the SMTP port used by your application to deliver e-mails to 35 | | users of the application. Like the host we have set this value to 36 | | stay compatible with the Mailgun e-mail application by default. 37 | | 38 | */ 39 | 'port' => env('MAIL_PORT', 587), 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Global "From" Address 43 | |-------------------------------------------------------------------------- 44 | | 45 | | You may wish for all e-mails sent by your application to be sent from 46 | | the same address. Here, you may specify a name and address that is 47 | | used globally for all e-mails that are sent by your application. 48 | | 49 | */ 50 | 'from' => [ 51 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 52 | 'name' => env('MAIL_FROM_NAME', 'Example'), 53 | ], 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | E-Mail Encryption Protocol 57 | |-------------------------------------------------------------------------- 58 | | 59 | | Here you may specify the encryption protocol that should be used when 60 | | the application send e-mail messages. A sensible default using the 61 | | transport layer security protocol should provide great security. 62 | | 63 | */ 64 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | SMTP Server Username 68 | |-------------------------------------------------------------------------- 69 | | 70 | | If your SMTP server requires a username for authentication, you should 71 | | set it here. This will get used to authenticate with your server on 72 | | connection. You may also set the "password" value below this one. 73 | | 74 | */ 75 | 'username' => env('MAIL_USERNAME'), 76 | 'password' => env('MAIL_PASSWORD'), 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Sendmail System Path 80 | |-------------------------------------------------------------------------- 81 | | 82 | | When using the "sendmail" driver to send e-mails, we will need to know 83 | | the path to where Sendmail lives on this server. A default path has 84 | | been provided here, which will work well on most of your systems. 85 | | 86 | */ 87 | 'sendmail' => '/usr/sbin/sendmail -bs', 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Markdown Mail Settings 91 | |-------------------------------------------------------------------------- 92 | | 93 | | If you are using Markdown based email rendering, you may configure your 94 | | theme and component paths here, allowing you to customize the design 95 | | of the emails. Or, you may simply stick with the Laravel defaults! 96 | | 97 | */ 98 | 'markdown' => [ 99 | 'theme' => 'default', 100 | 'paths' => [ 101 | resource_path('views/vendor/mail'), 102 | ], 103 | ], 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Log Channel 107 | |-------------------------------------------------------------------------- 108 | | 109 | | If you are using the "log" driver, you may specify the logging channel 110 | | if you prefer to keep mail messages separate from other log entries 111 | | for simpler reading. Otherwise, the default channel will be used. 112 | | 113 | */ 114 | 'log_channel' => env('MAIL_LOG_CHANNEL'), 115 | ]; 116 | -------------------------------------------------------------------------------- /config/permission.php: -------------------------------------------------------------------------------- 1 | [ 6 | 7 | /* 8 | * When using the "HasRoles" trait from this package, we need to know which 9 | * Eloquent model should be used to retrieve your permissions. Of course, it 10 | * is often just the "Permission" model but you may use whatever you like. 11 | * 12 | * The model you want to use as a Permission model needs to implement the 13 | * `Spatie\Permission\Contracts\Permission` contract. 14 | */ 15 | 16 | 'permission' => \App\Models\Permission::class, 17 | 18 | /* 19 | * When using the "HasRoles" trait from this package, we need to know which 20 | * Eloquent model should be used to retrieve your roles. Of course, it 21 | * is often just the "Role" model but you may use whatever you like. 22 | * 23 | * The model you want to use as a Role model needs to implement the 24 | * `Spatie\Permission\Contracts\Role` contract. 25 | */ 26 | 27 | 'role' => \App\Models\Role::class, 28 | 29 | ], 30 | 31 | 'table_names' => [ 32 | 33 | /* 34 | * When using the "HasRoles" trait from this package, we need to know which 35 | * table should be used to retrieve your roles. We have chosen a basic 36 | * default value but you may easily change it to any table you like. 37 | */ 38 | 39 | 'roles' => 'roles', 40 | 41 | /* 42 | * When using the "HasRoles" trait from this package, we need to know which 43 | * table should be used to retrieve your permissions. We have chosen a basic 44 | * default value but you may easily change it to any table you like. 45 | */ 46 | 47 | 'permissions' => 'permissions', 48 | 49 | /* 50 | * When using the "HasRoles" trait from this package, we need to know which 51 | * table should be used to retrieve your models permissions. We have chosen a 52 | * basic default value but you may easily change it to any table you like. 53 | */ 54 | 55 | 'model_has_permissions' => 'model_has_permissions', 56 | 57 | /* 58 | * When using the "HasRoles" trait from this package, we need to know which 59 | * table should be used to retrieve your models roles. We have chosen a 60 | * basic default value but you may easily change it to any table you like. 61 | */ 62 | 63 | 'model_has_roles' => 'model_has_roles', 64 | 65 | /* 66 | * When using the "HasRoles" trait from this package, we need to know which 67 | * table should be used to retrieve your roles permissions. We have chosen a 68 | * basic default value but you may easily change it to any table you like. 69 | */ 70 | 71 | 'role_has_permissions' => 'role_has_permissions', 72 | ], 73 | 74 | /* 75 | * By default all permissions will be cached for 24 hours unless a permission or 76 | * role is updated. Then the cache will be flushed immediately. 77 | */ 78 | 79 | 'cache_expiration_time' => 60 * 24, 80 | 81 | /* 82 | * By default we'll make an entry in the application log when the permissions 83 | * could not be loaded. Normally this only occurs while installing the packages. 84 | * 85 | * If for some reason you want to disable that logging, set this value to false. 86 | */ 87 | 88 | 'log_registration_exception' => env('APP_ENV') !== 'testing', 89 | ]; 90 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | 'block_for' => 0, 50 | ], 51 | 52 | 'sqs' => [ 53 | 'driver' => 'sqs', 54 | 'key' => env('AWS_ACCESS_KEY_ID'), 55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'), 58 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 59 | ], 60 | 61 | 'redis' => [ 62 | 'driver' => 'redis', 63 | 'connection' => 'default', 64 | 'queue' => env('REDIS_QUEUE', 'default'), 65 | 'retry_after' => 90, 66 | 'block_for' => null, 67 | ], 68 | 69 | ], 70 | 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Failed Queue Jobs 74 | |-------------------------------------------------------------------------- 75 | | 76 | | These options configure the behavior of failed queue job logging so you 77 | | can control which database and table are used to store the jobs that 78 | | have failed. You may change them to any database / table you wish. 79 | | 80 | */ 81 | 82 | 'failed' => [ 83 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database'), 84 | 'database' => env('DB_CONNECTION', 'mysql'), 85 | 'table' => 'failed_jobs', 86 | ], 87 | 88 | ]; 89 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | 'github' => [ 34 | 'client_id' => env('GITHUB_CLIENT_ID'), 35 | 'client_secret' => env('GITHUB_CLIENT_SECRET'), 36 | 'redirect' => 'http://example.com/callback-url', 37 | ], 38 | 39 | ]; 40 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 15 | resource_path('views'), 16 | ], 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Compiled View Path 20 | |-------------------------------------------------------------------------- 21 | | 22 | | This option determines where all the compiled Blade templates will be 23 | | stored for your application. Typically, this is within the storage 24 | | directory. However, as usual, you are free to change this value. 25 | | 26 | */ 27 | 'compiled' => env( 28 | 'VIEW_COMPILED_PATH', 29 | realpath(storage_path('framework/views')) 30 | ), 31 | ]; 32 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/PermissionFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 26 | 'uuid' => $this->faker->uuid, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/RoleFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 26 | 'uuid' => $this->faker->uuid, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/SocialProviderFactory.php: -------------------------------------------------------------------------------- 1 | User::factory()->create()->id, 27 | 'provider' => 'github', 28 | 'provider_id' => $this->faker->randomNumber(), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 27 | 'uuid' => $this->faker->uuid, 28 | 'email' => $this->faker->unique()->safeEmail, 29 | 'email_verified_at' => now(), 30 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 31 | 'remember_token' => Str::random(10), 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->uuid('uuid')->index()->unique(); 20 | $table->string('email')->unique(); 21 | $table->timestamp('email_verified_at')->nullable(); 22 | $table->string('password'); 23 | $table->rememberToken(); 24 | $table->timestamps(); 25 | $table->softDeletes(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('users'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token')->index(); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_02_09_031936_create_permission_tables.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 20 | $table->string('name'); 21 | $table->string('guard_name'); 22 | $table->uuid('uuid')->index()->unique(); 23 | $table->timestamps(); 24 | }); 25 | 26 | Schema::create($tableNames['roles'], function (Blueprint $table) { 27 | $table->bigIncrements('id'); 28 | $table->string('name'); 29 | $table->string('guard_name'); 30 | $table->uuid('uuid')->index()->unique(); 31 | $table->timestamps(); 32 | }); 33 | 34 | Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $foreignKeys) { 35 | $table->bigInteger('permission_id')->unsigned(); 36 | $table->morphs('model'); 37 | 38 | $table->foreign('permission_id') 39 | ->references('id') 40 | ->on($tableNames['permissions']) 41 | ->onDelete('cascade'); 42 | 43 | $table->primary(['permission_id', 'model_id', 'model_type']); 44 | }); 45 | 46 | Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $foreignKeys) { 47 | $table->bigInteger('role_id')->unsigned(); 48 | $table->morphs('model'); 49 | 50 | $table->foreign('role_id') 51 | ->references('id') 52 | ->on($tableNames['roles']) 53 | ->onDelete('cascade'); 54 | 55 | $table->primary(['role_id', 'model_id', 'model_type']); 56 | }); 57 | 58 | Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { 59 | $table->bigInteger('permission_id')->unsigned(); 60 | $table->bigInteger('role_id')->unsigned(); 61 | 62 | $table->foreign('permission_id') 63 | ->references('id') 64 | ->on($tableNames['permissions']) 65 | ->onDelete('cascade'); 66 | 67 | $table->foreign('role_id') 68 | ->references('id') 69 | ->on($tableNames['roles']) 70 | ->onDelete('cascade'); 71 | 72 | $table->primary(['permission_id', 'role_id']); 73 | }); 74 | } 75 | 76 | /** 77 | * Reverse the migrations. 78 | * 79 | * @return void 80 | */ 81 | public function down() 82 | { 83 | $tableNames = config('permission.table_names'); 84 | 85 | Schema::drop($tableNames['role_has_permissions']); 86 | Schema::drop($tableNames['model_has_roles']); 87 | Schema::drop($tableNames['model_has_permissions']); 88 | Schema::drop($tableNames['roles']); 89 | Schema::drop($tableNames['permissions']); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /database/migrations/2017_08_08_193843_create_assets_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->bigInteger('user_id')->unsigned()->nullable(); 19 | $table->uuid('uuid')->index()->unique(); 20 | $table->string('type', 45)->nullable(); 21 | $table->string('path', 45)->nullable(); 22 | $table->string('mime', 45)->nullable(); 23 | $table->timestamps(); 24 | $table->softDeletes(); 25 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('assets'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->text('connection'); 19 | $table->text('queue'); 20 | $table->longText('payload'); 21 | $table->longText('exception'); 22 | $table->timestamp('failed_at')->useCurrent(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('failed_jobs'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2021_01_10_023024_create_social_providers_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 19 | $table->string('provider')->index(); 20 | $table->string('provider_id')->index(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('social_providers'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(RoleTableSeeder::class); 19 | $this->call(UsersTableSeeder::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /database/seeders/Users/RoleTableSeeder.php: -------------------------------------------------------------------------------- 1 | 'Administrator'], 16 | ['name' => 'User'], 17 | ]; 18 | 19 | /** 20 | * @var array|\Illuminate\Support\Collection 21 | */ 22 | public $permissions = [ 23 | 'users' => [ 24 | ['name' => 'List users'], 25 | ['name' => 'Create users'], 26 | ['name' => 'Delete users'], 27 | ['name' => 'Update users'], 28 | ], 29 | 'roles' => [ 30 | ['name' => 'List roles'], 31 | ['name' => 'Create roles'], 32 | ['name' => 'Delete roles'], 33 | ['name' => 'Update roles'], 34 | ], 35 | 'permissions' => [ 36 | ['name' => 'List permissions'], 37 | ], 38 | ]; 39 | 40 | /** 41 | * Run the database seeders. 42 | * 43 | * @return void 44 | */ 45 | public function run() 46 | { 47 | $this->createRoles()->createPermissions()->assignAllPermissionsToAdminRole(); 48 | } 49 | 50 | /** 51 | * @return $this 52 | */ 53 | public function createRoles() 54 | { 55 | $this->roles = collect($this->roles)->map(function ($role) { 56 | return Role::create($role); 57 | }); 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return $this 64 | */ 65 | public function createPermissions() 66 | { 67 | $this->permissions = collect($this->permissions)->map(function ($group) { 68 | return collect($group)->map(function ($permission) { 69 | return Permission::create($permission); 70 | }); 71 | }); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * @return $this 78 | */ 79 | public function assignAllPermissionsToAdminRole() 80 | { 81 | $role = Role::where('name', 'Administrator')->firstOrFail(); 82 | $this->permissions->flatten()->each(function ($permission) use ($role) { 83 | $role->givePermissionTo($permission); 84 | }); 85 | 86 | return $this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /database/seeders/Users/UsersTableSeeder.php: -------------------------------------------------------------------------------- 1 | create([ 17 | 'name' => 'Jose Fonseca', 18 | 'email' => 'jose@example.com', 19 | ]); 20 | $user->assignRole('Administrator'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/api/blueprint/apidocs.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | HOST: https://example.com 3 | 4 | # Laravel API 5 | Welcome to the Starter Kit API, here you will find all the information related to the endpoints available. 6 | 7 | ## Authentication 8 | 9 | The API uses [oAuth2](https://oauth.net/2/) for authentication, this means that all API calls should contain and authorization that looks like this 10 | 11 | ``` 12 | Authorization: Bearer API_KEY_HERE 13 | ``` 14 | 15 | Please see the Authentication documentation to know how to get an access token. 16 | 17 | ## Headers 18 | 19 | Make sure you have the following content type headers are set on every request: 20 | 21 | ```http 22 | Accept: application/json 23 | Content-Type: application/json 24 | ``` 25 | 26 | ## Errors 27 | 28 | The API uses conventional HTTP response codes to indicate the success or failure of an API request. The table below contains a summary of the typical response codes: 29 | 30 | | Code | Description | 31 | |------|------------------------------------------------------------------------| 32 | | 200 | Everything is ok. | 33 | | 400 | Valid data was given but the request has failed. | 34 | | 401 | No valid API Key was given. | 35 | | 404 | The request resource could not be found. | 36 | | 405 | The method is not implemented | 37 | | 413 | The Body is too large | 38 | | 422 | The payload has missing required parameters or invalid data was given. | 39 | | 429 | Too many attempts. | 40 | | 500 | Request failed due to an internal error. | 41 | | 503 | API is offline for maintenance. | 42 | 43 | # Data Structures 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/api/blueprint/dataStructures/assets.apib: -------------------------------------------------------------------------------- 1 | ### Asset Object (object) 2 | + id: `08279320-dfc9-11e5-a450-0002a5d5c51b`(string) - The asset ID 3 | + mime: `image/jpeg` (string) - The asset mime type 4 | + type: `image` (string) - The asset type 5 | + path: `12342bh3y3bn3i4i5ii5b43o3n.jpg` (string) - The asset path in the storage 6 | + links 7 | + full: `https://laravelapi.test/api/assets/0c244c51-0a3b-4b86-829a-ee161c2f966f/render` (string) - The asset link for render full size 8 | + thumb: `https://laravelapi.test/api/assets/0c244c51-0a3b-4b86-829a-ee161c2f966f/render?width=200&height=200` (string) - The asset link for render thumb size 9 | + created_at : `1997-07-16T19:20:30+01:00` (string) - Date in format iso 8601 10 | -------------------------------------------------------------------------------- /docs/api/blueprint/dataStructures/auth.apib: -------------------------------------------------------------------------------- 1 | ### Registration input (object) 2 | - name: `Jose Fonseca`(string, required) - The name of the user 3 | - email: `email@example.com` (string, required) - The email of the user 4 | - password: `Password123**` (string, required) - The password of the user 5 | - password_confirmation: `Password123**` (string, required) - The password confirmation of the user 6 | 7 | ### Forgot password input (object) 8 | - email: `email@example.com` (string, required) - The email of the user 9 | 10 | ### Reset password input (object) 11 | - email: `email@example.com` (string, required) - The email of the user 12 | - token: `77d933717e14023a1ddcc4cfa0c1d20ccedeb3acb525092aae34ac1f3f708a51` (string, required) - The token received in the email 13 | - password: `Password123**` (string, required) - The new password of the user 14 | 15 | ### Reset password response (object) 16 | - message: `The email has been updated` (string) - The message after the reset 17 | 18 | ### Get tokens with credentials (object) 19 | - grant_type: `password` (string, required) - The grant type to use 20 | - client_id: `1` (string, required) - the client id 21 | - client_secret: `isudaiusyd87a6s87atsd8a7std` (string, required) - The client secret 22 | - username: `jose@example.com` (string, required) - The username for the user being authenticated 23 | - password: `Password123**` (string, required) - The password for the user being authenticated 24 | 25 | ### Get tokens with social token (object) 26 | - grant_type: `social_grant` (string, required) - The grant type to use 27 | - client_id: `1` (string, required) - the client id 28 | - client_secret: `isudaiusyd87a6s87atsd8a7std` (string, required) - The client secret 29 | - provider: `github` (string, required) - The provider to use 30 | - token: `sdoifu98a7sd87fytbs8d67ftnabs786dnta76std` (string, required) - The token obtained by the provider 31 | 32 | ### Tokens (object) 33 | - token_type: `Bearer` (string) - The token type 34 | - expires_in: `86400` (string) - When the token expires 35 | - access_token: `eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiNDNjYTE2ZDNjZTEwYmIyZDZjYmQwYjE1ZjU0ZmY2NDhiODkxZGVlYzJmZGE4YTE3NGEzNjE3NzZlMDEzMzdmMDcyMDVjYzM2OGUzYzkwMjUiLCJpYXQiOiIxNjEwMjQ3NTUwLjkwMzEzMSIsIm5iZiI6IjE2MTAyNDc1NTAuOTAzMTM3IiwiZXhwIjoiMTYxMDMzMzk1MC41MTc3ODAiLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.YNlBKazrYQ0CSFmWIUFeOl7ODDC3Sw-fiQyZ1Behyl7-_9fp7akuhmvGXeOJCYQiS1-tsYbkckaauVRlgpfeMExcxe261-8WWEGU0HQDaHxrXcojWQr5LwsnCno_elxLsqoXRZXrKe8s70zs6wwKnqc8tZkrUDkpFt4bjHHtCF1M4xGxA8kNNFs_sqyN0P-amWt1gfuMaALWxgYeISpAAzfP6KgqWVmP3JqDWQAIMhh8NO2kEkfE6rgxQXp0zzvlvvZ7RSYc0sV8JrSnwa15z-q7-1oQ4XFqmYxv-90i_Jrf5N3gz-VON6ovt26Vh7qNeHKFkf98DwHiu5XF6cDHpiZBKwfjlFzG6-0ZPN1ROIXPt5Bk35xLlUUgB6f5kPHZMPfMTuWGneeTbFm_5igdyxwaBqEVfVnuFPnIv5oaKKbAQleyo-yt_LZe9qxFNx-LFlMHmDyAqdfKkra0HG6dCfxynV6x2RtRZHyicN6fDPekTMBs356vNlRRL1dEEzZ4Fbpft4TRZylyqarAXfGb7kCKCuuuHbO8LT6f2yk5JW3Ngc-gfb1dfTO0bfgtl0Rg6l8PWGStSnmyXqeNxLej0XKhrPuqft1JFtucUCw8gUjrcuqSmAAzvXBTbLLvb65WSTnl_tOFviaSeBF-zCJxkxD2uJ1b31YuKMkVd-9dpMs` (string) - The Access token 36 | - refresh_token: `def50200cd296202fa3d84fb5fedb48f09453ac34afafaa05446bab2b0d927821b9c79ba8239ef2c1825fdee70d03e9504d3e739ec8d5efcf79b5cae61dd081972105d75d196b16214087a80df5c1a2616bbff9c22b75077ca7cc2c9c1176cb3fe1404d3ae3e868e01989ef432521839912fe8ffd39a4048ced88f0c4cd52d01fa7c25166bdab9ab104a419705444bf3b316f00e4b0b1edcee80b0b1b6ec748a62a974089994f215abdc2a6b537f0ceb93dbe0f828bbd338d00f4583ec992bb1bee5fec646082e5a38a1f35ed1931ed3a05b7a0e261261e3e31430d134392ffe6b4a4dcd4e8247b4646ad64e42533ebe9357794d331f77566b3e1b236f27afd4b29d44c109997042d918c2243d43a49e3847b084989639e9b5765b8da62f67f497f3b0a965f7cb36a1fecb738370537c8be4d432a518cbe78a0ebc8704fdd0111afe1bd705f9b5b0257aeb88da6c681a88219940e5ac7b9b686dd57ad4b466ab57` (string) - The Refresh token 37 | -------------------------------------------------------------------------------- /docs/api/blueprint/dataStructures/errors.apib: -------------------------------------------------------------------------------- 1 | ## Error 404 (object) 2 | - message: `404 Not found` (string) 3 | - status_code: 404 (number) `status code number` 4 | 5 | ## Error 400 (object) 6 | - message: `Bad Request` (string) 7 | - status_code: 400 (number) `status code number` 8 | 9 | ## Error 403 (object) 10 | - message: `Forbidden` (string) 11 | - status_code: 403 (number) `status code number` 12 | 13 | ## Error 401 (object) 14 | - message: `Unauthenticated.` (string) 15 | - status_code: 401 (number) `status code number` 16 | 17 | ## Error 405 (object) 18 | - message: `Method Not Allowed` (string) 19 | - status_code: 405 (number) `status code number` 20 | 21 | ## Error 422 (object) 22 | - message: `Validation error` (string) 23 | - errors (array) `Array of errors present in the validation` 24 | - status_code: 422 (number) `status code number` -------------------------------------------------------------------------------- /docs/api/blueprint/dataStructures/permissions.apib: -------------------------------------------------------------------------------- 1 | ## Permission Object (object) 2 | - id: `01020af0-44b5-11e7-8001-41a6bfe4ef85` (string) 3 | - name: `List users` (string) 4 | - created_at: `2017-02-09T03:28:32+00:00` (string) 5 | - updated_at: `2017-02-09T03:28:32+00:00` (string) 6 | 7 | 8 | ## Permission Pagination (object) 9 | - total: 30 (number) 10 | - count: 20 (number) 11 | - per_page: 20 (number) 12 | - current_page: 1 (number) 13 | - total_pages: 2 (number) 14 | - links 15 | - previous: `http://laravelapi.dev/api/roles?page=1` (string) 16 | - next: `http://laravelapi.dev/api/roles?page=2` (string) 17 | - last: `http://laravelapi.dev/api/roles?page=2` (string) -------------------------------------------------------------------------------- /docs/api/blueprint/dataStructures/roles.apib: -------------------------------------------------------------------------------- 1 | ## Role Object (object) 2 | - id: `01020af0-44b5-11e7-8001-41a6bfe4ef85` (string) 3 | - name: `Guest` (string) 4 | - created_at: `2017-02-09T03:28:32+00:00` (string) 5 | - updated_at: `2017-02-09T03:28:32+00:00` (string) 6 | - permissions (array) 7 | - (Permission Object) 8 | - (Permission Object) 9 | - (Permission Object) 10 | 11 | ## Role Create (object) 12 | - name: `Guest` (string) 13 | - permissions (array) 14 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Permission ID 15 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Permission ID 16 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Permission ID 17 | 18 | ## Role Update Full (object) 19 | - name: `Guest` (string) 20 | - permissions (array) 21 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Permission ID 22 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Permission ID 23 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Permission ID 24 | 25 | ## Role Update (object) 26 | - name: `Guest` (string) 27 | 28 | ## Role Pagination (object) 29 | - total: 30 (number) 30 | - count: 20 (number) 31 | - per_page: 20 (number) 32 | - current_page: 1 (number) 33 | - total_pages: 2 (number) 34 | - links 35 | - previous: `http://laravelapi.dev/api/roles?page=1` (string) 36 | - next: `http://laravelapi.dev/api/roles?page=2` (string) 37 | - last: `http://laravelapi.dev/api/roles?page=2` (string) -------------------------------------------------------------------------------- /docs/api/blueprint/dataStructures/users.apib: -------------------------------------------------------------------------------- 1 | ## User Object (object) 2 | - id: `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) 3 | - name: `Jhon Doe` (string) 4 | - email: `email@example.com` (string) 5 | - created_at: `2017-02-09T03:28:32+00:00` (string) 6 | - updated_at: `2017-02-09T03:28:32+00:00` (string) 7 | - roles 8 | - data (array) 9 | - (Role Object) 10 | - (Role Object) 11 | 12 | ## User Create (object) 13 | - name: `Jhon Doe` (string, required) 14 | - email: `email@example.com` (string, required) 15 | - password: `123456789` (string, required) 16 | - password_confirmation: `123456789` (string, required) 17 | - roles (array) 18 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Role ID 19 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Role ID 20 | - `d4d42ea0-ee77-11e6-b823-6d63c6504afc` (string) Role ID 21 | 22 | ## User Update (object) 23 | - name: `Jhon Doe` (string, optional) 24 | 25 | ## User Update Full (object) 26 | - name: `Jhon Doe` (string, required) 27 | - email: `email@example.com` (string, required) 28 | 29 | ## User Pagination (object) 30 | - total: 30 (number) 31 | - count: 20 (number) 32 | - per_page: 20 (number) 33 | - current_page: 1 (number) 34 | - total_pages: 2 (number) 35 | - links 36 | - previous: `http://laravelapi.dev/api/users?page=1` (string) 37 | - next: `http://laravelapi.dev/api/users?page=2` (string) 38 | - last: `http://laravelapi.dev/api/users?page=2` (string) 39 | 40 | ## User Update Password (object) 41 | - current_password: `12345` (string, required) 42 | - password: `123456789qq` (string, required) 43 | - password_confirmation: `123456789qq` (string, required) -------------------------------------------------------------------------------- /docs/api/blueprint/routes/auth.apib: -------------------------------------------------------------------------------- 1 | # Group Auth 2 | 3 | The auth API will allow you to work with the users registration and password management. 4 | 5 | ## Tokens [/oauth/token] 6 | 7 | ### Get a token [POST] 8 | Use this endpoint to get access tokens for the user, the API currently uses oAuth2 and supports 2 grants. 9 | - **Password:** Use grant `password` when the user is providing username and password 10 | - **Social grant:** Use grant `social_grant` when the user has a token from a supported OAuth provider. 11 | 12 | + Request (application/json) 13 | 14 | + Attributes (Get tokens with credentials) 15 | 16 | + Response 200 (application/json) 17 | 18 | + Attributes (Tokens) 19 | 20 | + Response 401 (application/json) 21 | 22 | + Attributes (Error 401) 23 | 24 | + Request (application/json) 25 | 26 | + Attributes (Get tokens with social token) 27 | 28 | + Response 200 (application/json) 29 | 30 | + Attributes (Tokens) 31 | 32 | + Response 401 (application/json) 33 | 34 | + Attributes (Error 401) 35 | 36 | ## Register [/api/register] 37 | Use this endpoint to register a new user from the client consuming the API. 38 | 39 | ### Register user [POST] 40 | This endpoint will allow you to handle the user registration in the API. 41 | 42 | + Request (application/json) 43 | 44 | + Attributes (Registration input) 45 | 46 | + Response 201 (application/json) 47 | 48 | + Attributes 49 | + data (User Object) 50 | 51 | + Response 422 (application/json) 52 | 53 | + Attributes (Error 422) 54 | 55 | 56 | ## Password Recovery [/api/passwords/reset] 57 | Use this endpoints to reset the user's password with a recovery email. 58 | 59 | ### Forgot Password [POST] 60 | This endpoint will allow you to trigger the recovery password email. 61 | 62 | + Request (application/json) 63 | 64 | + Attributes (Forgot password input) 65 | 66 | + Response 201 (application/json) 67 | 68 | + Response 422 (application/json) 69 | 70 | + Attributes (Error 422) 71 | 72 | ### Reset Password [PUT] 73 | This endpoint will allow you to update the user's password with the token received in the email. 74 | 75 | + Request (application/json) 76 | 77 | + Attributes (Reset password input) 78 | 79 | + Response 201 (application/json) 80 | 81 | + Attributes (Reset password response) 82 | 83 | + Response 422 (application/json) 84 | 85 | + Attributes (Error 422) 86 | 87 | -------------------------------------------------------------------------------- /docs/api/blueprint/routes/permissions.apib: -------------------------------------------------------------------------------- 1 | 2 | ## Permissions resource [/api/permissions] 3 | It requires your user to have permissions to fetch, create, update or delete roles in the system depending on the request you want to make 4 | 5 | ### List permissions [GET] 6 | 7 | This endpoint will allow you to get the roles registered in the system 8 | 9 | + Request (application/json) 10 | 11 | + Headers 12 | 13 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBlOGVhOGY0YWY2Yjg3NzU0YjZjZDBjNjdkNzBmMjdhYTZkMmRjNjU3YzIwYTU0MjQ3NWRlZGU4Y2VkNTc1N2IwZDcwMWFmNTAyMGU4MGE4In0.eyJhdWQiOiIxIiwianRpIjoiMGU4ZWE4ZjRhZjZiODc3NTRiNmNkMGM2N2Q3MGYyN2FhNmQyZGM2NTdjMjBhNTQyNDc1ZGVkZThjZWQ1NzU3YjBkNzAxYWY1MDIwZTgwYTgiLCJpYXQiOjE0NzYyOTk1NTQsIm5iZiI6MTQ3NjI5OTU1NCwiZXhwIjoxNDc3NTk1NTU0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.gTOotTVp2eF_HvuW48ngGqdly4bocNMlvY0al0YmYPJ_tjBQYegE4AjtCH2bWUf9aUz8tQqhMr2UFae3JV6VDQC9VzoGK8gU_nulM0BkqaCCxadqrw_slcd4he9hT0FE3WshvdZKcmgaVovgrI2-sAnX3n749BiWtWLEpw6x-TdEHEVcW6uRfRWyrwKhPBfOk-hXpgbecgT8LYdBW531P1ryjSJzzi5zBEe0Ecbp9Lo-fV69AW8ZBnG1DBjzxW-hvE0rRLXXnI4-f9rbQfK6QUzfG3Dg2INexcKxu9sQ3Vn5wHhQyu4_nTNQh8rsgphdAqFnEo-FQZrRWejkKbOi_BcpQybaXNn7Qln-96QF6PAzT-2E08VmL4XeVNcSvmM1sPVwirnSXo4UIFSsHvvIdVfEYEXD8XDonlnVO74RgBDMXWs6xo7dmDNaqQxKt9J_s_xtmyM3w62C4QucKw7MY0zOqviEyXySbrYbgazO_Pl1--GXtksE8tVMW8OW6Y8fw0JE0GEd5hZVadR277A5164QAJhiGXI_mKNTtUZVNaj0JpKEQpu4tod_BJR_DxzvcpKHwc1YrfrPOul6mL4kZeafkehPe79jxrCXKgKEuGlgSFcyrXUNCtME9LxsMID6QpJ-tfx4i0jqjixd_smqpjPPYqhPuqhox3uMLcDgM6s 14 | 15 | + Response 200 (application/json) 16 | 17 | + Attributes 18 | + data (array) 19 | + (Permission Object) 20 | + (Permission Object) 21 | + (Permission Object) 22 | + (Permission Object) 23 | + meta 24 | + pagination (Permission Pagination) 25 | 26 | + Response 401 (application/json) 27 | 28 | + Attributes (Error 401) 29 | 30 | + Response 403 (application/json) 31 | 32 | + Attributes (Error 403) 33 | 34 | + Response 404 (application/json) 35 | 36 | + Attributes (Error 404) 37 | -------------------------------------------------------------------------------- /docs/api/blueprint/routes/profile.apib: -------------------------------------------------------------------------------- 1 | ## Users profile [/api/me] 2 | 3 | ### Get logged in user profile [GET] 4 | 5 | + Request (application/json) 6 | 7 | + Headers 8 | 9 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBlOGVhOGY0YWY2Yjg3NzU0YjZjZDBjNjdkNzBmMjdhYTZkMmRjNjU3YzIwYTU0MjQ3NWRlZGU4Y2VkNTc1N2IwZDcwMWFmNTAyMGU4MGE4In0.eyJhdWQiOiIxIiwianRpIjoiMGU4ZWE4ZjRhZjZiODc3NTRiNmNkMGM2N2Q3MGYyN2FhNmQyZGM2NTdjMjBhNTQyNDc1ZGVkZThjZWQ1NzU3YjBkNzAxYWY1MDIwZTgwYTgiLCJpYXQiOjE0NzYyOTk1NTQsIm5iZiI6MTQ3NjI5OTU1NCwiZXhwIjoxNDc3NTk1NTU0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.gTOotTVp2eF_HvuW48ngGqdly4bocNMlvY0al0YmYPJ_tjBQYegE4AjtCH2bWUf9aUz8tQqhMr2UFae3JV6VDQC9VzoGK8gU_nulM0BkqaCCxadqrw_slcd4he9hT0FE3WshvdZKcmgaVovgrI2-sAnX3n749BiWtWLEpw6x-TdEHEVcW6uRfRWyrwKhPBfOk-hXpgbecgT8LYdBW531P1ryjSJzzi5zBEe0Ecbp9Lo-fV69AW8ZBnG1DBjzxW-hvE0rRLXXnI4-f9rbQfK6QUzfG3Dg2INexcKxu9sQ3Vn5wHhQyu4_nTNQh8rsgphdAqFnEo-FQZrRWejkKbOi_BcpQybaXNn7Qln-96QF6PAzT-2E08VmL4XeVNcSvmM1sPVwirnSXo4UIFSsHvvIdVfEYEXD8XDonlnVO74RgBDMXWs6xo7dmDNaqQxKt9J_s_xtmyM3w62C4QucKw7MY0zOqviEyXySbrYbgazO_Pl1--GXtksE8tVMW8OW6Y8fw0JE0GEd5hZVadR277A5164QAJhiGXI_mKNTtUZVNaj0JpKEQpu4tod_BJR_DxzvcpKHwc1YrfrPOul6mL4kZeafkehPe79jxrCXKgKEuGlgSFcyrXUNCtME9LxsMID6QpJ-tfx4i0jqjixd_smqpjPPYqhPuqhox3uMLcDgM6s 10 | 11 | + Response 200 (application/json) 12 | 13 | + Attributes 14 | + data (User Object) 15 | 16 | + Response 401 (application/json) 17 | 18 | + Attributes (Error 401) 19 | 20 | + Response 403 (application/json) 21 | 22 | + Attributes (Error 403) 23 | 24 | + Response 422 (application/json) 25 | 26 | + Attributes (Error 422) 27 | 28 | ### Full update logged in user profile [PUT] 29 | 30 | + Request (application/json) 31 | 32 | + Headers 33 | 34 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBlOGVhOGY0YWY2Yjg3NzU0YjZjZDBjNjdkNzBmMjdhYTZkMmRjNjU3YzIwYTU0MjQ3NWRlZGU4Y2VkNTc1N2IwZDcwMWFmNTAyMGU4MGE4In0.eyJhdWQiOiIxIiwianRpIjoiMGU4ZWE4ZjRhZjZiODc3NTRiNmNkMGM2N2Q3MGYyN2FhNmQyZGM2NTdjMjBhNTQyNDc1ZGVkZThjZWQ1NzU3YjBkNzAxYWY1MDIwZTgwYTgiLCJpYXQiOjE0NzYyOTk1NTQsIm5iZiI6MTQ3NjI5OTU1NCwiZXhwIjoxNDc3NTk1NTU0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.gTOotTVp2eF_HvuW48ngGqdly4bocNMlvY0al0YmYPJ_tjBQYegE4AjtCH2bWUf9aUz8tQqhMr2UFae3JV6VDQC9VzoGK8gU_nulM0BkqaCCxadqrw_slcd4he9hT0FE3WshvdZKcmgaVovgrI2-sAnX3n749BiWtWLEpw6x-TdEHEVcW6uRfRWyrwKhPBfOk-hXpgbecgT8LYdBW531P1ryjSJzzi5zBEe0Ecbp9Lo-fV69AW8ZBnG1DBjzxW-hvE0rRLXXnI4-f9rbQfK6QUzfG3Dg2INexcKxu9sQ3Vn5wHhQyu4_nTNQh8rsgphdAqFnEo-FQZrRWejkKbOi_BcpQybaXNn7Qln-96QF6PAzT-2E08VmL4XeVNcSvmM1sPVwirnSXo4UIFSsHvvIdVfEYEXD8XDonlnVO74RgBDMXWs6xo7dmDNaqQxKt9J_s_xtmyM3w62C4QucKw7MY0zOqviEyXySbrYbgazO_Pl1--GXtksE8tVMW8OW6Y8fw0JE0GEd5hZVadR277A5164QAJhiGXI_mKNTtUZVNaj0JpKEQpu4tod_BJR_DxzvcpKHwc1YrfrPOul6mL4kZeafkehPe79jxrCXKgKEuGlgSFcyrXUNCtME9LxsMID6QpJ-tfx4i0jqjixd_smqpjPPYqhPuqhox3uMLcDgM6s 35 | 36 | + Attributes (User Update Full) 37 | 38 | + Response 200 (application/json) 39 | 40 | + Attributes 41 | + data (User Object) 42 | 43 | + Response 401 (application/json) 44 | 45 | + Attributes (Error 401) 46 | 47 | + Response 403 (application/json) 48 | 49 | + Attributes (Error 403) 50 | 51 | + Response 422 (application/json) 52 | 53 | + Attributes (Error 422) 54 | 55 | ### Partial update logged in user profile [PATCH] 56 | 57 | + Request (application/json) 58 | 59 | + Headers 60 | 61 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBlOGVhOGY0YWY2Yjg3NzU0YjZjZDBjNjdkNzBmMjdhYTZkMmRjNjU3YzIwYTU0MjQ3NWRlZGU4Y2VkNTc1N2IwZDcwMWFmNTAyMGU4MGE4In0.eyJhdWQiOiIxIiwianRpIjoiMGU4ZWE4ZjRhZjZiODc3NTRiNmNkMGM2N2Q3MGYyN2FhNmQyZGM2NTdjMjBhNTQyNDc1ZGVkZThjZWQ1NzU3YjBkNzAxYWY1MDIwZTgwYTgiLCJpYXQiOjE0NzYyOTk1NTQsIm5iZiI6MTQ3NjI5OTU1NCwiZXhwIjoxNDc3NTk1NTU0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.gTOotTVp2eF_HvuW48ngGqdly4bocNMlvY0al0YmYPJ_tjBQYegE4AjtCH2bWUf9aUz8tQqhMr2UFae3JV6VDQC9VzoGK8gU_nulM0BkqaCCxadqrw_slcd4he9hT0FE3WshvdZKcmgaVovgrI2-sAnX3n749BiWtWLEpw6x-TdEHEVcW6uRfRWyrwKhPBfOk-hXpgbecgT8LYdBW531P1ryjSJzzi5zBEe0Ecbp9Lo-fV69AW8ZBnG1DBjzxW-hvE0rRLXXnI4-f9rbQfK6QUzfG3Dg2INexcKxu9sQ3Vn5wHhQyu4_nTNQh8rsgphdAqFnEo-FQZrRWejkKbOi_BcpQybaXNn7Qln-96QF6PAzT-2E08VmL4XeVNcSvmM1sPVwirnSXo4UIFSsHvvIdVfEYEXD8XDonlnVO74RgBDMXWs6xo7dmDNaqQxKt9J_s_xtmyM3w62C4QucKw7MY0zOqviEyXySbrYbgazO_Pl1--GXtksE8tVMW8OW6Y8fw0JE0GEd5hZVadR277A5164QAJhiGXI_mKNTtUZVNaj0JpKEQpu4tod_BJR_DxzvcpKHwc1YrfrPOul6mL4kZeafkehPe79jxrCXKgKEuGlgSFcyrXUNCtME9LxsMID6QpJ-tfx4i0jqjixd_smqpjPPYqhPuqhox3uMLcDgM6s 62 | 63 | + Attributes (User Update) 64 | 65 | + Response 200 (application/json) 66 | 67 | + Attributes 68 | + data (User Object) 69 | 70 | + Response 401 (application/json) 71 | 72 | + Attributes (Error 401) 73 | 74 | + Response 403 (application/json) 75 | 76 | + Attributes (Error 403) 77 | 78 | + Response 422 (application/json) 79 | 80 | + Attributes (Error 422) 81 | 82 | ### Change logged in user password [PUT /api/me/password] 83 | 84 | + Request (application/json) 85 | 86 | + Headers 87 | 88 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBlOGVhOGY0YWY2Yjg3NzU0YjZjZDBjNjdkNzBmMjdhYTZkMmRjNjU3YzIwYTU0MjQ3NWRlZGU4Y2VkNTc1N2IwZDcwMWFmNTAyMGU4MGE4In0.eyJhdWQiOiIxIiwianRpIjoiMGU4ZWE4ZjRhZjZiODc3NTRiNmNkMGM2N2Q3MGYyN2FhNmQyZGM2NTdjMjBhNTQyNDc1ZGVkZThjZWQ1NzU3YjBkNzAxYWY1MDIwZTgwYTgiLCJpYXQiOjE0NzYyOTk1NTQsIm5iZiI6MTQ3NjI5OTU1NCwiZXhwIjoxNDc3NTk1NTU0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.gTOotTVp2eF_HvuW48ngGqdly4bocNMlvY0al0YmYPJ_tjBQYegE4AjtCH2bWUf9aUz8tQqhMr2UFae3JV6VDQC9VzoGK8gU_nulM0BkqaCCxadqrw_slcd4he9hT0FE3WshvdZKcmgaVovgrI2-sAnX3n749BiWtWLEpw6x-TdEHEVcW6uRfRWyrwKhPBfOk-hXpgbecgT8LYdBW531P1ryjSJzzi5zBEe0Ecbp9Lo-fV69AW8ZBnG1DBjzxW-hvE0rRLXXnI4-f9rbQfK6QUzfG3Dg2INexcKxu9sQ3Vn5wHhQyu4_nTNQh8rsgphdAqFnEo-FQZrRWejkKbOi_BcpQybaXNn7Qln-96QF6PAzT-2E08VmL4XeVNcSvmM1sPVwirnSXo4UIFSsHvvIdVfEYEXD8XDonlnVO74RgBDMXWs6xo7dmDNaqQxKt9J_s_xtmyM3w62C4QucKw7MY0zOqviEyXySbrYbgazO_Pl1--GXtksE8tVMW8OW6Y8fw0JE0GEd5hZVadR277A5164QAJhiGXI_mKNTtUZVNaj0JpKEQpu4tod_BJR_DxzvcpKHwc1YrfrPOul6mL4kZeafkehPe79jxrCXKgKEuGlgSFcyrXUNCtME9LxsMID6QpJ-tfx4i0jqjixd_smqpjPPYqhPuqhox3uMLcDgM6s 89 | 90 | + Attributes (User Update Password) 91 | 92 | + Response 200 (application/json) 93 | 94 | + Attributes 95 | + data (User Object) 96 | 97 | + Response 401 (application/json) 98 | 99 | + Attributes (Error 401) 100 | 101 | + Response 403 (application/json) 102 | 103 | + Attributes (Error 403) 104 | 105 | + Response 422 (application/json) 106 | 107 | + Attributes (Error 422) 108 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./app 6 | 7 | 8 | 9 | 10 | ./tests/Unit 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes If Not A Folder... 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule ^(.*)/$ /$1 [L,R=301] 11 | 12 | # Handle Front Controller... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_FILENAME} !-f 15 | RewriteRule ^ index.php [L] 16 | 17 | # Handle Authorization Header 18 | RewriteCond %{HTTP:Authorization} . 19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Register The Auto Loader 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Composer provides a convenient, automatically generated class loader for 16 | | our application. We just need to utilize it! We'll simply require it 17 | | into the script here so that we don't have to worry about manual 18 | | loading any of our classes later on. It feels nice to relax. 19 | | 20 | */ 21 | 22 | require __DIR__.'/../bootstrap/autoload.php'; 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Turn On The Lights 27 | |-------------------------------------------------------------------------- 28 | | 29 | | We need to illuminate PHP development, so let us turn on the lights. 30 | | This bootstraps the framework and gets it ready for use, then it 31 | | will load up this application so that we can run it and send 32 | | the responses back to the browser and delight our users. 33 | | 34 | */ 35 | 36 | $app = require_once __DIR__.'/../bootstrap/app.php'; 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Run The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once we have the application, we can handle the incoming request 44 | | through the kernel, and send the associated response back to 45 | | the client's browser allowing them to enjoy the creative 46 | | and wonderful application we have prepared for them. 47 | | 48 | */ 49 | 50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 51 | 52 | $response = $kernel->handle( 53 | $request = Illuminate\Http\Request::capture() 54 | ); 55 | 56 | $response->send(); 57 | 58 | $kernel->terminate($request, $response); 59 | -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js", 3 | "/css/app.css": "/css/app.css" 4 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/vendor/scribe/css/highlight-atelier-cave-light.css: -------------------------------------------------------------------------------- 1 | /* Base16 Atelier Cave Light - Theme */ 2 | /* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ 3 | /* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ 4 | 5 | /* Atelier-Cave Comment */ 6 | .hljs-comment, 7 | .hljs-quote { 8 | color: #655f6d; 9 | } 10 | 11 | /* Atelier-Cave Red */ 12 | .hljs-variable, 13 | .hljs-template-variable, 14 | .hljs-attribute, 15 | .hljs-tag, 16 | .hljs-name, 17 | .hljs-regexp, 18 | .hljs-link, 19 | .hljs-name, 20 | .hljs-name, 21 | .hljs-selector-id, 22 | .hljs-selector-class { 23 | color: #be4678; 24 | } 25 | 26 | /* Atelier-Cave Orange */ 27 | .hljs-number, 28 | .hljs-meta, 29 | .hljs-built_in, 30 | .hljs-builtin-name, 31 | .hljs-literal, 32 | .hljs-type, 33 | .hljs-params { 34 | color: #aa573c; 35 | } 36 | 37 | /* Atelier-Cave Green */ 38 | .hljs-string, 39 | .hljs-symbol, 40 | .hljs-bullet { 41 | color: #2a9292; 42 | } 43 | 44 | /* Atelier-Cave Blue */ 45 | .hljs-title, 46 | .hljs-section { 47 | color: #576ddb; 48 | } 49 | 50 | /* Atelier-Cave Purple */ 51 | .hljs-keyword, 52 | .hljs-selector-tag { 53 | color: #955ae7; 54 | } 55 | 56 | .hljs-deletion, 57 | .hljs-addition { 58 | color: #19171c; 59 | display: inline-block; 60 | width: 100%; 61 | } 62 | 63 | .hljs-deletion { 64 | background-color: #be4678; 65 | } 66 | 67 | .hljs-addition { 68 | background-color: #2a9292; 69 | } 70 | 71 | .hljs { 72 | display: block; 73 | overflow-x: auto; 74 | background: #efecf4; 75 | color: #585260; 76 | padding: 0.5em; 77 | } 78 | 79 | .hljs-emphasis { 80 | font-style: italic; 81 | } 82 | 83 | .hljs-strong { 84 | font-weight: bold; 85 | } 86 | -------------------------------------------------------------------------------- /public/vendor/scribe/css/highlight-darcula.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Darcula color scheme from the JetBrains family of IDEs 4 | 5 | */ 6 | 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #2b2b2b; 13 | color: #bababa; 14 | } 15 | 16 | .hljs-strong, 17 | .hljs-emphasis { 18 | color: #a8a8a2; 19 | } 20 | 21 | .hljs-bullet, 22 | .hljs-quote, 23 | .hljs-link, 24 | .hljs-number, 25 | .hljs-regexp, 26 | .hljs-literal { 27 | color: #6896ba; 28 | } 29 | 30 | .hljs-code, 31 | .hljs-selector-class { 32 | color: #a6e22e; 33 | } 34 | 35 | .hljs-emphasis { 36 | font-style: italic; 37 | } 38 | 39 | .hljs-keyword, 40 | .hljs-selector-tag, 41 | .hljs-section, 42 | .hljs-attribute, 43 | .hljs-name, 44 | .hljs-variable { 45 | color: #cb7832; 46 | } 47 | 48 | .hljs-params { 49 | color: #b9b9b9; 50 | } 51 | 52 | .hljs-string { 53 | color: #6a8759; 54 | } 55 | 56 | .hljs-subst, 57 | .hljs-type, 58 | .hljs-built_in, 59 | .hljs-builtin-name, 60 | .hljs-symbol, 61 | .hljs-selector-id, 62 | .hljs-selector-attr, 63 | .hljs-selector-pseudo, 64 | .hljs-template-tag, 65 | .hljs-template-variable, 66 | .hljs-addition { 67 | color: #e0c46c; 68 | } 69 | 70 | .hljs-comment, 71 | .hljs-deletion, 72 | .hljs-meta { 73 | color: #7f7f7f; 74 | } 75 | -------------------------------------------------------------------------------- /public/vendor/scribe/css/highlight-monokai-sublime.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #23241f; 12 | } 13 | 14 | .hljs, 15 | .hljs-tag, 16 | .hljs-subst { 17 | color: #f8f8f2; 18 | } 19 | 20 | .hljs-strong, 21 | .hljs-emphasis { 22 | color: #a8a8a2; 23 | } 24 | 25 | .hljs-bullet, 26 | .hljs-quote, 27 | .hljs-number, 28 | .hljs-regexp, 29 | .hljs-literal, 30 | .hljs-link { 31 | color: #ae81ff; 32 | } 33 | 34 | .hljs-code, 35 | .hljs-title, 36 | .hljs-section, 37 | .hljs-selector-class { 38 | color: #a6e22e; 39 | } 40 | 41 | .hljs-strong { 42 | font-weight: bold; 43 | } 44 | 45 | .hljs-emphasis { 46 | font-style: italic; 47 | } 48 | 49 | .hljs-keyword, 50 | .hljs-selector-tag, 51 | .hljs-name, 52 | .hljs-attr { 53 | color: #f92672; 54 | } 55 | 56 | .hljs-symbol, 57 | .hljs-attribute { 58 | color: #66d9ef; 59 | } 60 | 61 | .hljs-params, 62 | .hljs-class .hljs-title { 63 | color: #f8f8f2; 64 | } 65 | 66 | .hljs-string, 67 | .hljs-type, 68 | .hljs-built_in, 69 | .hljs-builtin-name, 70 | .hljs-selector-id, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-addition, 74 | .hljs-variable, 75 | .hljs-template-variable { 76 | color: #e6db74; 77 | } 78 | 79 | .hljs-comment, 80 | .hljs-deletion, 81 | .hljs-meta { 82 | color: #75715e; 83 | } 84 | -------------------------------------------------------------------------------- /public/vendor/scribe/css/highlight-monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /public/vendor/scribe/css/highlight-vs2015.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Visual Studio 2015 dark style 3 | * Author: Nicolas LLOBERA 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | overflow-x: auto; 9 | padding: 0.5em; 10 | background: #1E1E1E; 11 | color: #DCDCDC; 12 | } 13 | 14 | .hljs-keyword, 15 | .hljs-literal, 16 | .hljs-symbol, 17 | .hljs-name { 18 | color: #569CD6; 19 | } 20 | .hljs-link { 21 | color: #569CD6; 22 | text-decoration: underline; 23 | } 24 | 25 | .hljs-built_in, 26 | .hljs-type { 27 | color: #4EC9B0; 28 | } 29 | 30 | .hljs-number, 31 | .hljs-class { 32 | color: #B8D7A3; 33 | } 34 | 35 | .hljs-string, 36 | .hljs-meta-string { 37 | color: #D69D85; 38 | } 39 | 40 | .hljs-regexp, 41 | .hljs-template-tag { 42 | color: #9A5334; 43 | } 44 | 45 | .hljs-subst, 46 | .hljs-function, 47 | .hljs-title, 48 | .hljs-params, 49 | .hljs-formula { 50 | color: #DCDCDC; 51 | } 52 | 53 | .hljs-comment, 54 | .hljs-quote { 55 | color: #57A64A; 56 | font-style: italic; 57 | } 58 | 59 | .hljs-doctag { 60 | color: #608B4E; 61 | } 62 | 63 | .hljs-meta, 64 | .hljs-meta-keyword, 65 | .hljs-tag { 66 | color: #9B9B9B; 67 | } 68 | 69 | .hljs-variable, 70 | .hljs-template-variable { 71 | color: #BD63C5; 72 | } 73 | 74 | .hljs-attr, 75 | .hljs-attribute, 76 | .hljs-builtin-name { 77 | color: #9CDCFE; 78 | } 79 | 80 | .hljs-section { 81 | color: gold; 82 | } 83 | 84 | .hljs-emphasis { 85 | font-style: italic; 86 | } 87 | 88 | .hljs-strong { 89 | font-weight: bold; 90 | } 91 | 92 | /*.hljs-code { 93 | font-family:'Monospace'; 94 | }*/ 95 | 96 | .hljs-bullet, 97 | .hljs-selector-tag, 98 | .hljs-selector-id, 99 | .hljs-selector-class, 100 | .hljs-selector-attr, 101 | .hljs-selector-pseudo { 102 | color: #D7BA7D; 103 | } 104 | 105 | .hljs-addition { 106 | background-color: #144212; 107 | display: inline-block; 108 | width: 100%; 109 | } 110 | 111 | .hljs-deletion { 112 | background-color: #600; 113 | display: inline-block; 114 | width: 100%; 115 | } 116 | -------------------------------------------------------------------------------- /public/vendor/scribe/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/public/vendor/scribe/images/logo.png -------------------------------------------------------------------------------- /public/vendor/scribe/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/public/vendor/scribe/images/navbar.png -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Laravel API Starter Kit 2 | 3 | [![Total Downloads](https://poser.pugx.org/joselfonseca/laravel-api/downloads.svg)](https://packagist.org/packages/joselfonseca/laravel-api) 4 | [![License](https://poser.pugx.org/laravel/framework/license.svg)](https://packagist.org/packages/laravel/framework) 5 | 6 | ![](https://dev-to-uploads.s3.amazonaws.com/i/4om2rgvulc688tlcj31c.jpg) 7 | 8 | Laravel API starter Kit will provide you with the tools for making API's that everyone will love, API Authentication is already provided with passport. 9 | 10 | Here is a list of the packages installed: 11 | 12 | - [Laravel Passport](https://laravel.com/docs/8.x/passport) 13 | - [Laravel Socialite](https://laravel.com/docs/8.x/socialite) 14 | - [Laravel Fractal](https://github.com/spatie/laravel-fractal) 15 | - [Laravel Permission](https://github.com/spatie/laravel-permission) 16 | - [Intervention Image](http://image.intervention.io/) 17 | 18 | ## Installation 19 | 20 | To install the project you can use composer 21 | 22 | ```bash 23 | composer create-project joselfonseca/laravel-api new-api 24 | ``` 25 | 26 | Modify the .env file to suit your needs 27 | 28 | ``` 29 | APP_NAME=Laravel 30 | APP_ENV=local 31 | APP_KEY= 32 | APP_DEBUG=true 33 | APP_URL=http://localhost 34 | 35 | LOG_CHANNEL=stack 36 | LOG_LEVEL=debug 37 | 38 | DB_CONNECTION=mysql 39 | DB_HOST=127.0.0.1 40 | DB_PORT=3306 41 | DB_DATABASE=laravel 42 | DB_USERNAME=root 43 | DB_PASSWORD= 44 | 45 | BROADCAST_DRIVER=log 46 | CACHE_DRIVER=file 47 | QUEUE_CONNECTION=sync 48 | SESSION_DRIVER=file 49 | SESSION_LIFETIME=120 50 | 51 | MEMCACHED_HOST=127.0.0.1 52 | 53 | REDIS_HOST=127.0.0.1 54 | REDIS_PASSWORD=null 55 | REDIS_PORT=6379 56 | 57 | MAIL_MAILER=smtp 58 | MAIL_HOST=mailhog 59 | MAIL_PORT=1025 60 | MAIL_USERNAME=null 61 | MAIL_PASSWORD=null 62 | MAIL_ENCRYPTION=null 63 | MAIL_FROM_ADDRESS=null 64 | MAIL_FROM_NAME="${APP_NAME}" 65 | 66 | AWS_ACCESS_KEY_ID= 67 | AWS_SECRET_ACCESS_KEY= 68 | AWS_DEFAULT_REGION=us-east-1 69 | AWS_BUCKET= 70 | 71 | PUSHER_APP_ID= 72 | PUSHER_APP_KEY= 73 | PUSHER_APP_SECRET= 74 | PUSHER_APP_CLUSTER=mt1 75 | 76 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 77 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 78 | ``` 79 | 80 | When you have the .env with your database connection set up you can run your migrations 81 | 82 | ```bash 83 | php artisan migrate 84 | ``` 85 | Then run `php artisan passport:install` 86 | 87 | Run `php artisan db:seed` and you should have a new user with the roles and permissions set up 88 | 89 | ## Tests 90 | 91 | Navigate to the project root and run `vendor/bin/phpunit` after installing all the composer dependencies and after the .env file was created. 92 | 93 | ## API documentation 94 | The project uses API blueprint as API spec and [Aglio](https://github.com/danielgtaylor/aglio) to render the API docs, please install aglio and [merge-apib](https://github.com/ValeriaVG/merge-apib) in your machine and then you can run the following command to compile and render the API docs 95 | ```bash 96 | composer api-docs 97 | ``` 98 | 99 | ## License 100 | 101 | The Laravel API Starter kit is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 102 | -------------------------------------------------------------------------------- /resources/docs/.filemtimes: -------------------------------------------------------------------------------- 1 | # GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE. 2 | # Scribe uses this file to know when you change something manually in your docs. 3 | resources/docs/groups/endpoints.md=1609361313 4 | resources/docs/index.md=1609361313 5 | resources/docs/authentication.md=1609361313 6 | resources/docs/groups/users.md=1609361313 -------------------------------------------------------------------------------- /resources/docs/authentication.md: -------------------------------------------------------------------------------- 1 | # Authenticating requests 2 | 3 | To authenticate requests, include an **`Authorization`** header with the value **`"Bearer {YOUR_AUTH_KEY}"`**. 4 | 5 | All authenticated endpoints are marked with a `requires authentication` badge in the documentation below. 6 | 7 | You can retrieve your token by login in with the passport routes. 8 | -------------------------------------------------------------------------------- /resources/docs/groups/endpoints.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/resources/docs/groups/endpoints.md -------------------------------------------------------------------------------- /resources/docs/groups/users.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | 4 | ## List users 5 | 6 | 7 | Returns the Users resource with the roles relation. 8 | 9 | > Example request: 10 | 11 | ```bash 12 | curl -X GET \ 13 | -G "http://localhost/api/users" \ 14 | -H "Content-Type: application/json" \ 15 | -H "Accept: application/json" 16 | ``` 17 | 18 | ```javascript 19 | const url = new URL( 20 | "http://localhost/api/users" 21 | ); 22 | 23 | let headers = { 24 | "Content-Type": "application/json", 25 | "Accept": "application/json", 26 | }; 27 | 28 | 29 | fetch(url, { 30 | method: "GET", 31 | headers, 32 | }).then(response => response.json()); 33 | ``` 34 | 35 | ```php 36 | 37 | $client = new \GuzzleHttp\Client(); 38 | $response = $client->get( 39 | 'http://localhost/api/users', 40 | [ 41 | 'headers' => [ 42 | 'Content-Type' => 'application/json', 43 | 'Accept' => 'application/json', 44 | ], 45 | ] 46 | ); 47 | $body = $response->getBody(); 48 | print_r(json_decode((string) $body)); 49 | ``` 50 | 51 | 52 | > Example response (200): 53 | 54 | ```json 55 | { 56 | "data": [ 57 | { 58 | "id": "fb8cab66-23e4-3f0b-8880-094f1ed59223", 59 | "name": "Mr. Dayton Pagac", 60 | "email": "urolfson@example.net", 61 | "created_at": "2020-12-30T20:48:33+00:00", 62 | "updated_at": "2020-12-30T20:48:33+00:00", 63 | "roles": { 64 | "data": [] 65 | } 66 | }, 67 | { 68 | "id": "6db61d41-17bc-3604-b7d5-83da76f9b03a", 69 | "name": "Annabel Senger", 70 | "email": "cstoltenberg@example.com", 71 | "created_at": "2020-12-30T20:48:33+00:00", 72 | "updated_at": "2020-12-30T20:48:33+00:00", 73 | "roles": { 74 | "data": [] 75 | } 76 | } 77 | ], 78 | "meta": { 79 | "pagination": { 80 | "total": 2, 81 | "count": 2, 82 | "per_page": 20, 83 | "current_page": 1, 84 | "total_pages": 1, 85 | "links": {} 86 | } 87 | } 88 | } 89 | ``` 90 | 94 | 98 |
99 |

100 | Request    101 |

102 |

103 | GET 104 | api/users 105 |

106 |
107 | 108 | 109 | ## Get single user 110 | 111 | 112 | Returns the User resource by it's uuid 113 | 114 | > Example request: 115 | 116 | ```bash 117 | curl -X GET \ 118 | -G "http://localhost/api/users/minima" \ 119 | -H "Content-Type: application/json" \ 120 | -H "Accept: application/json" 121 | ``` 122 | 123 | ```javascript 124 | const url = new URL( 125 | "http://localhost/api/users/minima" 126 | ); 127 | 128 | let headers = { 129 | "Content-Type": "application/json", 130 | "Accept": "application/json", 131 | }; 132 | 133 | 134 | fetch(url, { 135 | method: "GET", 136 | headers, 137 | }).then(response => response.json()); 138 | ``` 139 | 140 | ```php 141 | 142 | $client = new \GuzzleHttp\Client(); 143 | $response = $client->get( 144 | 'http://localhost/api/users/minima', 145 | [ 146 | 'headers' => [ 147 | 'Content-Type' => 'application/json', 148 | 'Accept' => 'application/json', 149 | ], 150 | ] 151 | ); 152 | $body = $response->getBody(); 153 | print_r(json_decode((string) $body)); 154 | ``` 155 | 156 | 157 | > Example response (200): 158 | 159 | ```json 160 | { 161 | "data": { 162 | "id": "c6eaae09-1a89-33ed-93a5-6c8528d810d8", 163 | "name": "Kaelyn Lemke", 164 | "email": "alta.heller@example.com", 165 | "created_at": "2020-12-30T20:48:33+00:00", 166 | "updated_at": "2020-12-30T20:48:33+00:00", 167 | "roles": { 168 | "data": [] 169 | } 170 | } 171 | } 172 | ``` 173 | 177 | 181 |
182 |

183 | Request    184 |

185 |

186 | GET 187 | api/users/{uuid} 188 |

189 |

URL Parameters

190 |

191 | uuid  string   192 | 193 |
194 | The UUID of the user.

195 |
196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /resources/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Laravel API 3 | 4 | language_tabs: 5 | - bash 6 | - javascript 7 | - php 8 | 9 | includes: 10 | - "./prepend.md" 11 | - "./authentication.md" 12 | - "./groups/*" 13 | - "./errors.md" 14 | - "./append.md" 15 | 16 | logo: 17 | 18 | toc_footers: 19 | - View Postman collection 20 | - View OpenAPI (Swagger) spec 21 | - Documentation powered by Scribe ✍ 22 | 23 | --- 24 | 25 | # Introduction 26 | 27 | An API starter kit for your next project 28 | 29 | This documentation aims to provide all the information you need to work with our API. 30 | 31 | 33 | 34 | 35 | > Base URL 36 | 37 | ```yaml 38 | http://localhost 39 | ``` -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have e-mailed your password reset link!', 18 | 'token' => 'This password reset token is invalid.', 19 | 'user' => "We can't find a user with that e-mail address.", 20 | 'throttled' => 'Please wait before retrying.', 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | ['auth:api']], function () { 14 | Route::group(['prefix' => 'users'], function () { 15 | Route::get('/', 'Api\Users\UsersController@index'); 16 | Route::post('/', 'Api\Users\UsersController@store'); 17 | Route::get('/{uuid}', 'Api\Users\UsersController@show'); 18 | Route::put('/{uuid}', 'Api\Users\UsersController@update'); 19 | Route::patch('/{uuid}', 'Api\Users\UsersController@update'); 20 | Route::delete('/{uuid}', 'Api\Users\UsersController@destroy'); 21 | }); 22 | 23 | Route::group(['prefix' => 'roles'], function () { 24 | Route::get('/', 'Api\Users\RolesController@index'); 25 | Route::post('/', 'Api\Users\RolesController@store'); 26 | Route::get('/{uuid}', 'Api\Users\RolesController@show'); 27 | Route::put('/{uuid}', 'Api\Users\RolesController@update'); 28 | Route::patch('/{uuid}', 'Api\Users\RolesController@update'); 29 | Route::delete('/{uuid}', 'Api\Users\RolesController@destroy'); 30 | }); 31 | 32 | Route::get('permissions', 'Api\Users\PermissionsController@index'); 33 | 34 | Route::group(['prefix' => 'me'], function () { 35 | Route::get('/', 'Api\Users\ProfileController@index'); 36 | Route::put('/', 'Api\Users\ProfileController@update'); 37 | Route::patch('/', 'Api\Users\ProfileController@update'); 38 | Route::put('/password', 'Api\Users\ProfileController@updatePassword'); 39 | }); 40 | 41 | Route::group(['prefix' => 'assets'], function () { 42 | Route::post('/', 'Api\Assets\UploadFileController@store'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | info('Token for user '.$user->name); 19 | $token = $user->createToken('Personal Access Token')->accessToken; 20 | $this->info($token); 21 | })->describe('Generates a personal access token for a user'); 22 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 20 | 21 | Hash::setRounds(4); 22 | 23 | return $app; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/ApiDocsTest.php: -------------------------------------------------------------------------------- 1 | get('/') 14 | ->assertSeeText('The API uses conventional HTTP response codes to indicate the success or failure of an API request. The table below contains a summary of the typical response codes'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Feature/Assets/RenderFileTest.php: -------------------------------------------------------------------------------- 1 | create()); 20 | $server = $this->transformHeadersToServerVars([ 21 | 'Content-Type' => 'image/png', 22 | 'Content-Length' => mb_strlen($file), 23 | ]); 24 | $response = $this->call('POST', 'api/assets', [], [], [], $server, $file); 25 | $asset = json_decode($response->getContent()); 26 | $response = $this->get('api/assets/'.$asset->data->id.'/render'); 27 | $response->assertStatus(200); 28 | $headers = $response->headers; 29 | $this->assertTrue($headers->has('Content-Type')); 30 | $this->assertEquals('image/png', $headers->get('Content-Type')); 31 | } 32 | 33 | public function test_it_renders_placeholder_image() 34 | { 35 | Passport::actingAs(User::factory()->create()); 36 | $response = $this->get('api/assets/'.Str::uuid().'/render'); 37 | $response->assertStatus(200); 38 | $headers = $response->headers; 39 | $this->assertTrue($headers->has('Content-Type')); 40 | $this->assertEquals('image/jpeg', $headers->get('Content-Type')); 41 | } 42 | 43 | public function test_it_renders_placeholder_image_resized_to_width_100() 44 | { 45 | Passport::actingAs(User::factory()->create()); 46 | $response = $this->get('api/assets/'.Str::uuid().'/render?width=100'); 47 | $response->assertStatus(200); 48 | $headers = $response->headers; 49 | $this->assertTrue($headers->has('Content-Type')); 50 | $this->assertEquals('image/jpeg', $headers->get('Content-Type')); 51 | Storage::put('created.jpeg', $response->getContent()); 52 | $size = getimagesize(storage_path('app/created.jpeg')); 53 | $this->assertEquals(100, $size[0]); 54 | $this->assertEquals(100, $size[1]); 55 | Storage::delete('created.jpeg'); 56 | } 57 | 58 | public function test_it_renders_placeholder_image_resized_to_height_100() 59 | { 60 | Passport::actingAs(User::factory()->create()); 61 | $response = $this->get('api/assets/'.Str::uuid().'/render?height=100'); 62 | $response->assertStatus(200); 63 | $headers = $response->headers; 64 | $this->assertTrue($headers->has('Content-Type')); 65 | $this->assertEquals('image/jpeg', $headers->get('Content-Type')); 66 | Storage::put('created.jpeg', $response->getContent()); 67 | $size = getimagesize(storage_path('app/created.jpeg')); 68 | $this->assertEquals(100, $size[0]); 69 | $this->assertEquals(100, $size[1]); 70 | Storage::delete('created.jpeg'); 71 | } 72 | 73 | public function test_it_renders_placeholder_image_resized_to_width_and_height() 74 | { 75 | Passport::actingAs(User::factory()->create()); 76 | $response = $this->get('api/assets/'.Str::uuid().'/render?height=100&width=300'); 77 | $response->assertStatus(200); 78 | $headers = $response->headers; 79 | $this->assertTrue($headers->has('Content-Type')); 80 | $this->assertEquals('image/jpeg', $headers->get('Content-Type')); 81 | Storage::put('created.jpeg', $response->getContent()); 82 | $size = getimagesize(storage_path('app/created.jpeg')); 83 | $this->assertEquals(300, $size[0]); 84 | $this->assertEquals(100, $size[1]); 85 | Storage::delete('created.jpeg'); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Feature/Assets/UploadImageTest.php: -------------------------------------------------------------------------------- 1 | expectsEvents([AssetWasCreated::class]); 20 | $file = base64_encode(file_get_contents(base_path('tests/Resources/pic.png'))); 21 | Passport::actingAs(User::factory()->create()); 22 | $server = $this->transformHeadersToServerVars([ 23 | 'Content-Type' => 'image/png', 24 | 'Content-Length' => mb_strlen($file), 25 | ]); 26 | $response = $this->call('POST', 'api/assets', [], [], [], $server, $file); 27 | $image = json_decode($response->getContent()); 28 | $this->assertEquals(201, $response->getStatusCode()); 29 | $response->assertJson([ 30 | 'data' => [ 31 | 'type' => 'image', 32 | 'mime' => 'image/png', 33 | ], 34 | ]); 35 | $this->assertDatabaseHas('assets', [ 36 | 'type' => 'image', 37 | 'mime' => 'image/png', 38 | 'uuid' => $image->data->id, 39 | ]); 40 | $this->assertTrue(Storage::has($image->data->path)); 41 | } 42 | 43 | public function test_it_verifies_max_file_size() 44 | { 45 | $this->doesntExpectEvents([AssetWasCreated::class]); 46 | Passport::actingAs(User::factory()->create()); 47 | $file = base64_encode(file_get_contents(base_path('tests/Resources/bigpic.jpg'))); 48 | $server = $this->transformHeadersToServerVars([ 49 | 'Content-Type' => 'image/jpeg', 50 | 'Content-Length' => mb_strlen($file), 51 | 'Accept' => 'application/json', 52 | ]); 53 | $response = $this->call('POST', 'api/assets', [], [], [], $server, $file); 54 | $this->assertEquals(413, $response->getStatusCode()); 55 | } 56 | 57 | public function test_it_validates_mime_type() 58 | { 59 | $this->doesntExpectEvents([AssetWasCreated::class]); 60 | Passport::actingAs(User::factory()->create()); 61 | $server = $this->transformHeadersToServerVars([ 62 | 'Content-Type' => 'application/xml', 63 | 'Content-Length' => mb_strlen('some ramdon content'), 64 | 'Accept' => 'application/json', 65 | ]); 66 | $response = $this->call('POST', 'api/assets', [], [], [], $server, 'some ramdon content'); 67 | $this->assertEquals(422, $response->getStatusCode()); 68 | $jsonResponse = json_decode($response->getContent(), true); 69 | $this->assertArrayHasKey('Content-Type', $jsonResponse['errors']); 70 | } 71 | 72 | public function test_it_uploads_from_url() 73 | { 74 | $this->expectsEvents([AssetWasCreated::class]); 75 | Passport::actingAs(User::factory()->create()); 76 | $response = $this->json('POST', 'api/assets', [ 77 | 'url' => 'https://images.unsplash.com/photo-1657299156538-e08595d224ca?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80', 78 | ]); 79 | $image = json_decode($response->getContent()); 80 | $this->assertEquals(201, $response->getStatusCode()); 81 | $response->assertJson([ 82 | 'data' => [ 83 | 'type' => 'image', 84 | 'mime' => 'image/jpeg', 85 | ], 86 | ]); 87 | $this->assertDatabaseHas('assets', [ 88 | 'type' => 'image', 89 | 'mime' => 'image/jpeg', 90 | 'uuid' => $image->data->id, 91 | ]); 92 | $this->assertTrue(Storage::has($image->data->path)); 93 | } 94 | 95 | public function test_it_respond_validation_unreachable_error_in_url() 96 | { 97 | $this->doesntExpectEvents([AssetWasCreated::class]); 98 | Passport::actingAs(User::factory()->create()); 99 | $response = $this->json('POST', 'api/assets', [ 100 | 'url' => 'http://somedomain/350x150', 101 | ], [ 102 | 'Accept' => 'application/json', 103 | ]); 104 | $jsonResponse = json_decode($response->getContent(), true); 105 | $this->assertEquals(422, $response->getStatusCode()); 106 | $this->assertArrayHasKey('url', $jsonResponse['errors']); 107 | } 108 | 109 | public function test_it_validates_mime_on_url() 110 | { 111 | $this->doesntExpectEvents([AssetWasCreated::class]); 112 | Passport::actingAs(User::factory()->create()); 113 | $response = $this->json('POST', 'api/assets', [ 114 | 'url' => 'http://google.com', 115 | ], [ 116 | 'Accept' => 'application/json', 117 | ]); 118 | $jsonResponse = json_decode($response->getContent(), true); 119 | $this->assertEquals(422, $response->getStatusCode()); 120 | $this->assertArrayHasKey('Content-Type', $jsonResponse['errors']); 121 | } 122 | 123 | public function test_it_validates_url() 124 | { 125 | Passport::actingAs(User::factory()->create()); 126 | $response = $this->json('POST', 'api/assets', [ 127 | 'url' => 'http://somedomain.com/350x150', 128 | ]); 129 | $jsonResponse = json_decode($response->getContent(), true); 130 | $this->assertEquals(422, $response->getStatusCode()); 131 | $this->assertArrayHasKey('Content-Type', $jsonResponse['errors']); 132 | } 133 | 134 | public function test_it_validates_size_using_multipart_file() 135 | { 136 | Storage::fake(); 137 | Passport::actingAs(User::factory()->create()); 138 | config()->set('files.maxsize', 10); 139 | $file = UploadedFile::fake()->image('avatar.jpg')->size(1000); 140 | $response = $this->post('api/assets', [ 141 | 'file' => $file, 142 | ], [ 143 | 'Accept' => 'application/json', 144 | ]); 145 | $jsonResponse = json_decode($response->getContent(), true); 146 | $this->assertEquals(413, $response->getStatusCode()); 147 | $this->assertArrayHasKey('message', $jsonResponse); 148 | $this->assertEquals('The body is too large', $jsonResponse['message']); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/Feature/Auth/PasswordsTest.php: -------------------------------------------------------------------------------- 1 | create(); 25 | $this->postJson('/api/passwords/reset', [ 26 | 'email' => $user->email, 27 | ])->assertCreated(); 28 | Notification::assertSentTo($user, ResetPassword::class); 29 | Event::assertDispatched(ForgotPasswordRequested::class, function ($event) use ($user) { 30 | return $event->email === $user->email; 31 | }); 32 | } 33 | 34 | public function test_it_validates_input() 35 | { 36 | Notification::fake(); 37 | Event::fake([ForgotPasswordRequested::class]); 38 | $this->postJson('/api/passwords/reset', [])->assertStatus(422); 39 | Notification::assertNothingSent(); 40 | Event::assertNotDispatched(ForgotPasswordRequested::class); 41 | } 42 | 43 | public function test_it_recovers_password_with_token() 44 | { 45 | Event::fake([PasswordRecovered::class]); 46 | $user = User::factory()->create(); 47 | $broker = Password::broker(); 48 | $token = $broker->createToken($user); 49 | DB::table('password_resets')->insert([ 50 | 'email' => $user->email, 51 | 'token' => $token, 52 | 'created_at' => now(), 53 | ]); 54 | $this->putJson('/api/passwords/reset', [ 55 | 'token' => $token, 56 | 'email' => $user->email, 57 | 'password' => 'Abc123**', 58 | ])->assertOk(); 59 | Event::assertDispatched(PasswordRecovered::class, function ($event) use ($user) { 60 | return $event->user->id === $user->id; 61 | }); 62 | } 63 | 64 | public function test_it_validates_token_in_password_reset() 65 | { 66 | Event::fake([PasswordRecovered::class]); 67 | $user = User::factory()->create(); 68 | $this->putJson('/api/passwords/reset', [ 69 | 'token' => 'some-token', 70 | 'email' => $user->email, 71 | 'password' => 'Abc123**', 72 | ])->assertStatus(422); 73 | Event::assertNotDispatched(PasswordRecovered::class); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Feature/Auth/RegisterTest.php: -------------------------------------------------------------------------------- 1 | seed(); 20 | $this->app->make(PermissionRegistrar::class)->registerPermissions(); 21 | } 22 | 23 | public function test_it_register_user_with_role() 24 | { 25 | Event::fake([Registered::class]); 26 | $response = $this->json('POST', 'api/register/', [ 27 | 'name' => 'John Doe', 28 | 'email' => 'john@example.com', 29 | 'password' => '12345678', 30 | 'password_confirmation' => '12345678', 31 | ]); 32 | $response->assertStatus(201); 33 | $this->assertDatabaseHas('users', [ 34 | 'name' => 'John Doe', 35 | 'email' => 'john@example.com', 36 | ]); 37 | $user = User::where('email', 'john@example.com')->first(); 38 | $this->assertTrue($user->hasRole('User')); 39 | Event::assertDispatched(Registered::class, function ($event) use ($user) { 40 | return $user->id === $event->user->id; 41 | }); 42 | } 43 | 44 | public function test_it_validates_input_for_registration() 45 | { 46 | Event::fake([Registered::class]); 47 | $response = $this->json('POST', 'api/register', [ 48 | 'name' => 'Some User', 49 | 'email' => 'some@email.com', 50 | 'password' => '123456789qq', 51 | ]); 52 | $response->assertStatus(422); 53 | $this->assertDatabaseMissing('users', [ 54 | 'name' => 'Some User', 55 | 'email' => 'some@email.com', 56 | ]); 57 | Event::assertNotDispatched(Registered::class); 58 | } 59 | 60 | public function test_it_returns_422_on_validation_error() 61 | { 62 | Event::fake([Registered::class]); 63 | $response = $this->json('POST', 'api/register', [ 64 | 'name' => 'Some User', 65 | ]); 66 | $response->assertStatus(422); 67 | $this->assertEquals('{"message":"The email field is required. (and 1 more error)","status_code":422,"errors":{"email":["The email field is required."],"password":["The password field is required."]}}', $response->getContent()); 68 | $this->assertDatabaseMissing('users', [ 69 | 'name' => 'Some User', 70 | 'email' => 'some@email.com', 71 | ]); 72 | Event::assertNotDispatched(Registered::class); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Feature/Auth/SocialiteTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getId') 23 | ->andReturn($provider->provider_id) 24 | ->shouldReceive('getName') 25 | ->andReturn($provider->user->name) 26 | ->shouldReceive('getEmail') 27 | ->andReturn($provider->user->email); 28 | Socialite::shouldReceive('driver->userFromToken')->andReturn($abstractUser); 29 | } 30 | 31 | public function mockSocialiteWithoutUser() 32 | { 33 | $abstractUser = Mockery::mock('Laravel\Socialite\Two\User'); 34 | $abstractUser 35 | ->shouldReceive('getId') 36 | ->andReturn('fakeId') 37 | ->shouldReceive('getName') 38 | ->andReturn('Jose Fonseca') 39 | ->shouldReceive('getEmail') 40 | ->andReturn('jose@example.com'); 41 | Socialite::shouldReceive('driver->userFromToken')->andReturn($abstractUser); 42 | } 43 | 44 | public function mockSocialiteWithUser(User $user) 45 | { 46 | $abstractUser = Mockery::mock('Laravel\Socialite\Two\User'); 47 | $abstractUser 48 | ->shouldReceive('getId') 49 | ->andReturn('fakeId') 50 | ->shouldReceive('getName') 51 | ->andReturn('Jose Fonseca') 52 | ->shouldReceive('getEmail') 53 | ->andReturn($user->email); 54 | Socialite::shouldReceive('driver->userFromToken')->andReturn($abstractUser); 55 | } 56 | 57 | public function createClient() 58 | { 59 | return Client::factory()->create(); 60 | } 61 | 62 | public function test_it_generates_tokens_with_social_grant_for_existing_user() 63 | { 64 | $provider = SocialProvider::factory()->create(); 65 | $this->mockSocialite($provider); 66 | $client = $this->createClient(); 67 | $response = $this->postJson('oauth/token', [ 68 | 'grant_type' => 'social_grant', 69 | 'client_id' => $client->id, 70 | 'client_secret' => $client->secret, 71 | 'token' => Str::random(), 72 | 'provider' => 'github', 73 | ])->assertOk(); 74 | $decodedResponse = json_decode($response->getContent(), true); 75 | $this->assertArrayHasKey('access_token', $decodedResponse); 76 | $this->assertArrayHasKey('refresh_token', $decodedResponse); 77 | } 78 | 79 | public function test_it_generates_tokens_with_social_grant_for_non_existing_user() 80 | { 81 | $this->mockSocialiteWithoutUser(); 82 | $client = $this->createClient(); 83 | $response = $this->postJson('oauth/token', [ 84 | 'grant_type' => 'social_grant', 85 | 'client_id' => $client->id, 86 | 'client_secret' => $client->secret, 87 | 'token' => Str::random(), 88 | 'provider' => 'github', 89 | ])->assertOk(); 90 | $decodedResponse = json_decode($response->getContent(), true); 91 | $this->assertArrayHasKey('access_token', $decodedResponse); 92 | $this->assertArrayHasKey('refresh_token', $decodedResponse); 93 | $this->assertDatabaseHas('users', [ 94 | 'email' => 'jose@example.com', 95 | 'name' => 'Jose Fonseca', 96 | ]); 97 | $createdUser = User::first(); 98 | $this->assertDatabaseHas('social_providers', [ 99 | 'user_id' => $createdUser->id, 100 | 'provider' => 'github', 101 | 'provider_id' => 'fakeId', 102 | ]); 103 | } 104 | 105 | public function test_it_generates_tokens_with_social_grant_for_existing_user_without_social_provider() 106 | { 107 | $user = User::factory()->create(); 108 | $this->mockSocialiteWithUser($user); 109 | $this->assertDatabaseMissing('social_providers', [ 110 | 'user_id' => $user->id, 111 | ]); 112 | $client = $this->createClient(); 113 | $response = $this->postJson('oauth/token', [ 114 | 'grant_type' => 'social_grant', 115 | 'client_id' => $client->id, 116 | 'client_secret' => $client->secret, 117 | 'token' => Str::random(), 118 | 'provider' => 'github', 119 | ])->assertOk(); 120 | $decodedResponse = json_decode($response->getContent(), true); 121 | $this->assertArrayHasKey('access_token', $decodedResponse); 122 | $this->assertArrayHasKey('refresh_token', $decodedResponse); 123 | $this->assertDatabaseHas('social_providers', [ 124 | 'user_id' => $user->id, 125 | 'provider' => 'github', 126 | 'provider_id' => 'fakeId', 127 | ]); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/Feature/PingTest.php: -------------------------------------------------------------------------------- 1 | json('GET', 'api/ping'); 18 | $response->assertStatus(200); 19 | $response->assertJson(['status' => 'ok']); 20 | } 21 | 22 | public function test_it_returns_404() 23 | { 24 | $response = $this->json('GET', 'api/non-existing-resource'); 25 | $response->assertStatus(404); 26 | $response->assertJson(['message' => '404 Not Found', 'status_code' => 404]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Feature/Users/PermissionsEndpointsTest.php: -------------------------------------------------------------------------------- 1 | seed(); 20 | $this->app->make(PermissionRegistrar::class)->registerPermissions(); 21 | } 22 | 23 | public function test_it_can_list_permissions() 24 | { 25 | Passport::actingAs(User::first()); 26 | $response = $this->json('GET', 'api/permissions'); 27 | $response->assertStatus(200); 28 | $response->assertJson([ 29 | 'data' => [ 30 | ['name' => 'List users'], 31 | ['name' => 'Create users'], 32 | ], 33 | 'meta' => [ 34 | 'pagination' => [ 35 | 36 | ], 37 | ], 38 | ]); 39 | } 40 | 41 | public function test_it_can_list_paginated_permissions() 42 | { 43 | Permission::factory()->count(30)->create(); 44 | Passport::actingAs(User::first()); 45 | $response = $this->json('GET', 'api/permissions?limit=10'); 46 | $response->assertStatus(200); 47 | $response->assertJson([ 48 | 'data' => [ 49 | ['name' => 'List users'], 50 | ['name' => 'Create users'], 51 | ], 52 | 'meta' => [ 53 | 'pagination' => [ 54 | 55 | ], 56 | ], 57 | ]); 58 | $jsonResponse = json_decode($response->getContent(), true); 59 | $queryString = explode('?', $jsonResponse['meta']['pagination']['links']['next']); 60 | parse_str($queryString[1], $result); 61 | $this->assertArrayHasKey('limit', $result); 62 | $this->assertEquals('10', $result['limit']); 63 | } 64 | 65 | public function test_it_prevents_unauthorized_list_permissions() 66 | { 67 | Passport::actingAs(User::factory()->create()); 68 | $response = $this->json('GET', 'api/permissions'); 69 | $response->assertStatus(403); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Feature/Users/ProfileEndpointsTest.php: -------------------------------------------------------------------------------- 1 | seed(); 20 | $this->app->make(PermissionRegistrar::class)->registerPermissions(); 21 | } 22 | 23 | public function test_it_gets_user_profile() 24 | { 25 | Passport::actingAs(User::first()); 26 | $response = $this->json('GET', 'api/me'); 27 | $response->assertStatus(200); 28 | $response->assertJson([ 29 | 'data' => [ 30 | 'name' => 'Jose Fonseca', 31 | 'email' => 'jose@example.com', 32 | 'roles' => [], 33 | ], 34 | ]); 35 | } 36 | 37 | public function test_it_can_update_logged_user_profile_all_entity() 38 | { 39 | Passport::actingAs(User::first()); 40 | $response = $this->json('PUT', '/api/me', [ 41 | 'name' => 'Jose Fonseca Edited', 42 | 'email' => 'jose@example.com', 43 | ]); 44 | $response->assertStatus(200); 45 | $this->assertDatabaseHas('users', [ 46 | 'name' => 'Jose Fonseca Edited', 47 | 'email' => 'jose@example.com', 48 | ]); 49 | $response->assertJson([ 50 | 'data' => [ 51 | 'name' => 'Jose Fonseca Edited', 52 | 'email' => 'jose@example.com', 53 | 'roles' => [], 54 | ], 55 | ]); 56 | } 57 | 58 | public function test_it_can_update_profile_partial_entity() 59 | { 60 | Passport::actingAs(User::first()); 61 | $response = $this->json('PATCH', '/api/me', [ 62 | 'name' => 'Jose Fonseca Edited', 63 | ]); 64 | $response->assertStatus(200); 65 | $this->assertDatabaseHas('users', [ 66 | 'name' => 'Jose Fonseca Edited', 67 | 'email' => 'jose@example.com', 68 | ]); 69 | $response->assertJson([ 70 | 'data' => [ 71 | 'name' => 'Jose Fonseca Edited', 72 | 'email' => 'jose@example.com', 73 | 'roles' => [], 74 | ], 75 | ]); 76 | } 77 | 78 | public function test_it_validates_input_for_update_profile() 79 | { 80 | Passport::actingAs(User::first()); 81 | $response = $this->json('PATCH', '/api/me', [ 82 | 'name' => '', 83 | ]); 84 | $response->assertStatus(422); 85 | } 86 | 87 | public function test_it_validates_input_for_email_on_update_profile() 88 | { 89 | Passport::actingAs(User::first()); 90 | $user = User::factory()->create(); 91 | $response = $this->json('PATCH', '/api/me', [ 92 | 'email' => $user->email, 93 | ]); 94 | $response->assertStatus(422); 95 | } 96 | 97 | public function test_it_updates_logged_in_user_password() 98 | { 99 | $user = User::first(); 100 | Passport::actingAs($user); 101 | $response = $this->json('PUT', '/api/me/password', [ 102 | 'current_password' => 'password', 103 | 'password' => '123456789qq', 104 | 'password_confirmation' => '123456789qq', 105 | ]); 106 | $response->assertStatus(200); 107 | $this->assertTrue(app(Hasher::class)->check('123456789qq', $user->fresh()->password)); 108 | } 109 | 110 | public function test_it_validates_input_to_update_logged_in_user_password_giving_wrong_current_pass() 111 | { 112 | $user = User::first(); 113 | Passport::actingAs($user); 114 | $response = $this->json('PUT', '/api/me/password', [ 115 | 'current_password' => 'secret1234345', 116 | 'password' => '123456789qq', 117 | 'password_confirmation' => '123456789qq', 118 | ]); 119 | $response->assertStatus(422); 120 | $this->assertFalse(app(Hasher::class)->check('123456789qq', $user->fresh()->password)); 121 | } 122 | 123 | public function test_it_validates_input_to_update_logged_in_user_password() 124 | { 125 | $user = User::first(); 126 | Passport::actingAs($user); 127 | $response = $this->json('PUT', '/api/me/password', [ 128 | 'current_password' => 'secret1234', 129 | 'password' => '12345', 130 | 'password_confirmation' => '123456789qq', 131 | ]); 132 | $response->assertStatus(422); 133 | $this->assertFalse(app(Hasher::class)->check('123456789qq', $user->fresh()->password)); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/Resources/bigpic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/tests/Resources/bigpic.jpg -------------------------------------------------------------------------------- /tests/Resources/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/tests/Resources/pic.png -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | create(); 17 | $permissions = Permission::factory()->count(3)->create(); 18 | $role->syncPermissions($permissions); 19 | $permissions->each(function ($permission) use ($role) { 20 | $this->assertDatabaseHas('role_has_permissions', [ 21 | 'role_id' => $role->id, 22 | 'permission_id' => $permission->id, 23 | ]); 24 | }); 25 | } 26 | 27 | public function test_it_syncs_permissions_by_array_of_names() 28 | { 29 | $role = Role::factory()->create(); 30 | $permissions = Permission::factory()->count(3)->create(); 31 | $role->syncPermissions($permissions->pluck('name')->toArray()); 32 | $permissions->each(function ($permission) use ($role) { 33 | $this->assertDatabaseHas('role_has_permissions', [ 34 | 'role_id' => $role->id, 35 | 'permission_id' => $permission->id, 36 | ]); 37 | }); 38 | } 39 | 40 | public function test_it_syncs_permissions_by_array_of_uuids() 41 | { 42 | $role = Role::factory()->create(); 43 | $permissions = Permission::factory()->count(3)->create(); 44 | $role->syncPermissions($permissions->pluck('uuid')->toArray()); 45 | $permissions->each(function ($permission) use ($role) { 46 | $this->assertDatabaseHas('role_has_permissions', [ 47 | 'role_id' => $role->id, 48 | 'permission_id' => $permission->id, 49 | ]); 50 | }); 51 | } 52 | 53 | public function test_it_can_fill_uuid_at_creation() 54 | { 55 | $uuid = '84e28c10-8991-11e7-ad89-056674746d73'; 56 | 57 | $roleNotFilled = Role::factory()->create(); 58 | $this->assertNotEquals($uuid, $roleNotFilled->uuid); 59 | 60 | $roleFilled = Role::factory()->create(['uuid' => $uuid]); 61 | $this->assertEquals($uuid, $roleFilled->uuid); 62 | } 63 | } 64 | --------------------------------------------------------------------------------