diff --git a/.env.example b/.env.example index 64c98fb..bcbbca2 100644 --- a/.env.example +++ b/.env.example @@ -8,18 +8,27 @@ LOG_CHANNEL=stack LOG_LEVEL=debug DB_CONNECTION=mysql -DB_HOST=127.0.0.1 +DB_HOST=mysql DB_PORT=3306 -DB_DATABASE=davorminchorov_api -DB_USERNAME=root -DB_PASSWORD= +DB_DATABASE=davorminchorov +DB_USERNAME=davorminchorov +DB_PASSWORD=davorminchorov + +DB_TEST_CONNECTION=mysql_testing +DB_TEST_HOST=mysql_testing +DB_TEST_PORT=3307 +DB_TEST_DATABASE=davorminchorov_testing +DB_TEST_USERNAME=davorminchorov_testing +DB_TEST_PASSWORD=davorminchorov_testing BROADCAST_DRIVER=log CACHE_DRIVER=file FILESYSTEM_DRIVER=local QUEUE_CONNECTION=sync + SESSION_DRIVER=file SESSION_LIFETIME=120 +SESSION_DOMAIN=davorminchorov.test MEMCACHED_HOST=127.0.0.1 @@ -49,3 +58,7 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +OCTANE_SERVER=swoole + +SANCTUM_STATEFUL_DOMAINS= diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 8299713..e0f12fa 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -1,4 +1,4 @@ -name: Merge me! +name: Auto Merge Dependabot Pull Requests on: workflow_run: @@ -6,7 +6,8 @@ on: - completed workflows: - 'Fix PHP Code Styles' - - 'Run PHPStan for Laravel' + - 'Run PHPStan Analysis' + - 'Run PHPUnit Tests' jobs: merge-me: @@ -19,7 +20,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} uses: ridedott/merge-me-action@v2 with: - # Depending on branch prodtection rules, a manually populated + # Depending on branch protection rules, a manually populated # `GITHUB_TOKEN_WORKAROUND` secret with permissions to push to # a protected branch must be used. # diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index b3e3a2f..66d8319 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -1,4 +1,4 @@ -name: Run PHPStan for Laravel +name: Run PHPStan Analysis on: push: diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 0000000..e1dd428 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,51 @@ +name: Run PHPUnit Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + phpunit: + runs-on: ubuntu-latest + timeout-minutes: 5 + env: + BROADCAST_DRIVER: log + CACHE_DRIVER: file + QUEUE_CONNECTION: sync + SESSION_DRIVER: array + DB_CONNECTION: mysql + DB_HOST: 127.0.0.1 + DB_PASSWORD: davorminchorov_testing + DB_USERNAME: davorminchorov_testing + DB_DATABASE: davorminchorov_testing + services: + mysql: + image: mysql:latest + ports: + - 3306:3306 + env: + MYSQL_DATABASE: davorminchorov_testing + MYSQL_USER: davorminchorov_testing + MYSQL_PASSWORD: davorminchorov_testing + MYSQL_ROOT_PASSWORD: root + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + with: + php-version: 8.0 + extensions: dom, curl, mbstring, zip, pcntl, intl + coverage: xdebug + - run: composer install --no-interaction --ignore-platform-reqs --no-scripts + - run: ./vendor/bin/phpunit --testdox --coverage-html=.coverage --coverage-text + - uses: actions/upload-artifact@v2 + with: + name: Coverage + path: ./.coverage + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: Logs + path: ./storage/logs diff --git a/.gitignore b/.gitignore index 6177dcb..df20484 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ yarn-error.log /.idea /.vscode .php-cs-fixer.cache +.phpstorm.meta.php +_ide_helper.php diff --git a/README.md b/README.md index 0a424b8..856e849 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # Davor Minchorov API +[![Fix PHP Code Styles](https://github.com/davorminchorov/api.davorminchorov.com/actions/workflows/phpcs.yml/badge.svg?branch=main)](https://github.com/davorminchorov/api.davorminchorov.com/actions/workflows/phpcs.yml) +[![Run PHPStan Analysis](https://github.com/davorminchorov/api.davorminchorov.com/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/davorminchorov/api.davorminchorov.com/actions/workflows/phpstan.yml) +[![Run PHPUnit Tests](https://github.com/davorminchorov/api.davorminchorov.com/actions/workflows/phpunit.yml/badge.svg?branch=main)](https://github.com/davorminchorov/api.davorminchorov.com/actions/workflows/phpunit.yml) + The REST API for Davor Minchorov's personal website and blog. + +For the front end application, please see: [davorminchorov/davorminchorov.com](https://github.com/davorminchorov/davorminchorov.com) diff --git a/app/Authentication/Actions/LoginAction.php b/app/Authentication/Actions/LoginAction.php new file mode 100644 index 0000000..a56b2de --- /dev/null +++ b/app/Authentication/Actions/LoginAction.php @@ -0,0 +1,51 @@ +getUserByEmailQuery)(email: $loginDataTransferObject->email); + + ($this->validateUserPasswordRule)( + password: $loginDataTransferObject->password, + userPassword: $user->password + ); + + $user->access_token = ($this->createAccessTokenAction)(user: $user, tokenName: AccessTokenName::API_AUTHENTICATION); + + return $user; + } +} diff --git a/app/Authentication/Actions/LogoutAction.php b/app/Authentication/Actions/LogoutAction.php new file mode 100644 index 0000000..87e8e40 --- /dev/null +++ b/app/Authentication/Actions/LogoutAction.php @@ -0,0 +1,37 @@ +authManager->guard()->user(); + + return ($this->deleteAccessTokenAction)(tokenName: AccessTokenName::API_AUTHENTICATION, user: $user); + } +} diff --git a/app/Authentication/Api/V1/ApiResources/LoginJsonResource.php b/app/Authentication/Api/V1/ApiResources/LoginJsonResource.php new file mode 100644 index 0000000..72bbc06 --- /dev/null +++ b/app/Authentication/Api/V1/ApiResources/LoginJsonResource.php @@ -0,0 +1,27 @@ + $this->uuid, + 'type' => 'users', + 'attributes' => [ + 'name' => $this->name, + 'accessToken' => $this->access_token, + ], + ]; + } +} diff --git a/app/Authentication/Api/V1/Controllers/LoginController.php b/app/Authentication/Api/V1/Controllers/LoginController.php new file mode 100644 index 0000000..e4be97c --- /dev/null +++ b/app/Authentication/Api/V1/Controllers/LoginController.php @@ -0,0 +1,38 @@ +loginAction)( + loginDataTransferObject: LoginDataTransferObject::fromRequest($loginFormRequest) + ) + ); + } +} diff --git a/app/Authentication/Api/V1/Controllers/LogoutController.php b/app/Authentication/Api/V1/Controllers/LogoutController.php new file mode 100644 index 0000000..bdafc0b --- /dev/null +++ b/app/Authentication/Api/V1/Controllers/LogoutController.php @@ -0,0 +1,32 @@ +logoutAction)(); + + return new Response(content: null, status: SymfonyResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Authentication/Api/V1/FormRequests/LoginFormRequest.php b/app/Authentication/Api/V1/FormRequests/LoginFormRequest.php new file mode 100644 index 0000000..4138517 --- /dev/null +++ b/app/Authentication/Api/V1/FormRequests/LoginFormRequest.php @@ -0,0 +1,29 @@ + ['required', 'string', 'email'], + 'password' => ['required', 'string'] + ]; + } +} diff --git a/app/Authentication/DataTransferObjects/LoginDataTransferObject.php b/app/Authentication/DataTransferObjects/LoginDataTransferObject.php new file mode 100644 index 0000000..f3bf4ab --- /dev/null +++ b/app/Authentication/DataTransferObjects/LoginDataTransferObject.php @@ -0,0 +1,35 @@ + $loginFormRequest->input('email'), + 'password' => $loginFormRequest->input('password'), + ]); + } +} diff --git a/app/Authentication/Routes/api.php b/app/Authentication/Routes/api.php index f0e8ef4..9bb2294 100644 --- a/app/Authentication/Routes/api.php +++ b/app/Authentication/Routes/api.php @@ -1,9 +1,9 @@ router->get('/', function () { - return response()->json([ - 'version' => 1, - 'name' => 'Davor Minchorov API', - 'module' => 'Authentication', - ]); -}); +use DavorMinchorov\Authentication\Api\V1\Controllers\LoginController; +use DavorMinchorov\Authentication\Api\V1\Controllers\LogoutController; + +/** @var \Illuminate\Routing\Router $router */ + +$router->post('/login', [LoginController::class, '__invoke'])->name('login'); +$router->post('/logout', [LogoutController::class, '__invoke'])->name('logout')->middleware('auth:sanctum'); diff --git a/app/Authentication/Rules/ValidateUserPasswordRule.php b/app/Authentication/Rules/ValidateUserPasswordRule.php new file mode 100644 index 0000000..6a5c8f2 --- /dev/null +++ b/app/Authentication/Rules/ValidateUserPasswordRule.php @@ -0,0 +1,38 @@ +hasher->check(value: $password, hashedValue: $userPassword ?? '')) { + return true; + } + + throw ValidationException::withMessages(messages: [ + 'email' => ['The provided credentials are incorrect.'], + ]); + } +} diff --git a/app/Authentication/Tests/Feature/AuthenticationTest.php b/app/Authentication/Tests/Feature/AuthenticationTest.php new file mode 100644 index 0000000..2c507a0 --- /dev/null +++ b/app/Authentication/Tests/Feature/AuthenticationTest.php @@ -0,0 +1,142 @@ +create(); + + $response = $this->postJson(route($this->loginRouteName), [ + 'email' => $user->email, + 'password' => 'password', + ]); + + $jsonResponse = $response->json(); + + $response->assertStatus(Response::HTTP_OK); + $response->assertExactJson([ + 'data' => [ + 'id' => $user->uuid, + 'type' => 'users', + 'attributes' => [ + 'name' => $user->name, + 'accessToken' => $jsonResponse['data']['attributes']['accessToken'], + ], + ] + ]); + + $this->assertCount(1, $user->tokens); + } + + /** + * @test + */ + public function a_user_gets_unauthorized_error_with_when_trying_to_login_with_invalid_credentials(): void + { + $user = User::factory()->create(); + + $response = $this->postJson(route($this->loginRouteName), [ + 'email' => 'test@example.com', + 'password' => 'secret', + ]); + + $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); + $response->assertExactJson([ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'email' => [ + 'The provided credentials are incorrect.' + ] + ] + ]); + } + + /** + * @test + */ + public function a_user_can_logout(): void + { + $user = User::factory()->create(); + + Sanctum::actingAs($user); + + $response = $this->postJson(route($this->logoutRouteName)); + + $response->assertNoContent(); + + $this->assertCount(0, $user->tokens); + } + + /** + * @test + */ + public function a_guest_cannot_logout(): void + { + $response = $this->postJson(route($this->logoutRouteName)); + + $response->assertUnauthorized(); + } + + /** + * @test + * @dataProvider loginValidationDataProvider + * + * @param string $field + * @param mixed $value + * + * @return void + */ + public function check_login_validation_errors(string $field, mixed $value): void + { + $user = User::factory()->create(); + + $response = $this->postJson(route($this->loginRouteName), [ + 'email' => $user->email, + 'password' => 'password', + $field => $value, + ]); + + $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); + $response->assertJsonValidationErrors($field); + } + + /** + * The data provider for the login validation errors. + * + * @return array + */ + public function loginValidationDataProvider(): array + { + return [ + 'The email field is required' => ['email', ''], + 'The password field is required' => ['password', ''], + 'The email must be a valid email address' => [ + 'email', + 'invalidemailaddress' + ], + ]; + } +} diff --git a/app/Authentication/Tests/Unit/ExampleTest.php b/app/Authentication/Tests/Unit/ExampleTest.php new file mode 100644 index 0000000..66bfd68 --- /dev/null +++ b/app/Authentication/Tests/Unit/ExampleTest.php @@ -0,0 +1,16 @@ +assertTrue(true); + } +} diff --git a/app/Framework/Http/Middleware/RedirectIfAuthenticated.php b/app/Framework/Http/Middleware/RedirectIfAuthenticated.php index 7ec2f07..a7ff766 100644 --- a/app/Framework/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Framework/Http/Middleware/RedirectIfAuthenticated.php @@ -3,17 +3,18 @@ namespace DavorMinchorov\Framework\Http\Middleware; use Closure; -use Illuminate\Contracts\Auth\Guard; +use Illuminate\Auth\AuthManager; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class RedirectIfAuthenticated { /** - * @param Guard $guard + * @param AuthManager $authManager */ - public function __construct(private Guard $guard) + public function __construct(private AuthManager $authManager) { + } /** @@ -27,10 +28,12 @@ public function __construct(private Guard $guard) */ public function handle(Request $request, Closure $next, ...$guards): mixed { - if ($this->guard->check()) { - return new JsonResponse([ - 'message' => 'Already authenticated.' - ]); + foreach($guards as $guard) { + if ($this->authManager->guard($guard)->check()) { + return new JsonResponse([ + 'message' => 'Already authenticated.' + ]); + } } return $next($request); diff --git a/app/Framework/Providers/AuthServiceProvider.php b/app/Framework/Providers/AuthServiceProvider.php deleted file mode 100644 index f879be3..0000000 --- a/app/Framework/Providers/AuthServiceProvider.php +++ /dev/null @@ -1,19 +0,0 @@ - [ 'name' => 'Framework', - 'path' => 'app/Framework/Routes/api.php', - 'prefix' => 'v1', - 'middleware' => ['api'], - 'namespace' => 'DavorMinchorov\Framework\Api\V1\Controllers', + 'api_routes_path' => 'app/Framework/Routes/api.php', + 'api_prefix' => 'v1', + 'api_route_name_prefix' => 'v1.framework.', + 'api_middleware' => ['api'], + 'api_namespace' => 'DavorMinchorov\Framework\Api\V1\Controllers', ], 'authentication' => [ 'name' => 'Authentication', - 'path' => 'app/Authentication/Routes/api.php', - 'prefix' => 'v1/authentication', - 'middleware' => ['api'], - 'namespace' => 'DavorMinchorov\Authentication\Api\V1\Controllers', + 'api_routes_path' => 'app/Authentication/Routes/api.php', + 'api_prefix' => 'v1/authentication', + 'api_route_name_prefix' => 'v1.authentication.', + 'api_middleware' => ['api'], + 'api_namespace' => 'DavorMinchorov\Authentication\Api\V1\Controllers', ], 'users' => [ 'name' => 'Users', - 'path' => 'app/Users/Routes/api.php', - 'prefix' => 'v1/users', - 'middleware' => ['api'], - 'namespace' => 'DavorMinchorov\Users\Api\V1\Controllers', + 'api_routes_path' => 'app/Users/Routes/api.php', + 'api_prefix' => 'v1/users', + 'api_route_name_prefix' => 'v1.users.', + 'api_middleware' => ['api'], + 'api_namespace' => 'DavorMinchorov\Users\Api\V1\Controllers', + ], + + 'personalAccessTokens' => [ + 'name' => 'PersonalAccessTokens', + 'api_routes_path' => 'app/PersonalAccessTokens/Routes/api.php', + 'api_prefix' => 'v1/personalAccessTokens', + 'api_route_name_prefix' => 'v1.personalAccessTokens.', + 'api_middleware' => ['api'], + 'api_namespace' => 'DavorMinchorov\PersonalAccessTokens\Api\V1\Controllers', ], ]; @@ -45,10 +57,11 @@ public function boot(): void $this->routes(function () { collect($this->modules)->map(function ($module) { - Route::prefix($module['prefix']) - ->middleware($module['middleware']) - ->namespace($module['namespace']) - ->group(base_path($module['path'])); + Route::prefix($module['api_prefix']) + ->middleware($module['api_middleware']) + ->namespace($module['api_namespace']) + ->name($module['api_route_name_prefix']) + ->group(base_path($module['api_routes_path'])); }); }); } diff --git a/app/Framework/Providers/AppServiceProvider.php b/app/Framework/Providers/SanctumServiceProvider.php similarity index 62% rename from app/Framework/Providers/AppServiceProvider.php rename to app/Framework/Providers/SanctumServiceProvider.php index ebb31d0..69103aa 100644 --- a/app/Framework/Providers/AppServiceProvider.php +++ b/app/Framework/Providers/SanctumServiceProvider.php @@ -2,9 +2,11 @@ namespace DavorMinchorov\Framework\Providers; +use DavorMinchorov\PersonalAccessTokens\Models\PersonalAccessToken; use Illuminate\Support\ServiceProvider; +use Laravel\Sanctum\Sanctum; -class AppServiceProvider extends ServiceProvider +class SanctumServiceProvider extends ServiceProvider { /** * Register any application services. @@ -23,6 +25,6 @@ public function register(): void */ public function boot(): void { - // + Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); } } diff --git a/app/Framework/Routes/api.php b/app/Framework/Routes/api.php index c711e69..b3d9bbc 100644 --- a/app/Framework/Routes/api.php +++ b/app/Framework/Routes/api.php @@ -1,9 +1 @@ router->get('/', function () { - return response()->json([ - 'version' => 1, - 'name' => 'Davor Minchorov API', - 'module' => 'Framework', - ]); -}); diff --git a/app/Framework/Tests/CreatesApplication.php b/app/Framework/Tests/CreatesApplication.php index e47ead6..e0214b6 100644 --- a/app/Framework/Tests/CreatesApplication.php +++ b/app/Framework/Tests/CreatesApplication.php @@ -3,17 +3,18 @@ namespace DavorMinchorov\Framework\Tests; use Illuminate\Contracts\Console\Kernel; +use Illuminate\Foundation\Application; trait CreatesApplication { /** * Creates the application. * - * @return \Illuminate\Foundation\Application + * @return Application */ - public function createApplication() + public function createApplication(): Application { - $app = require __DIR__.'/../bootstrap/app.php'; + $app = require __DIR__ . '/../../../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); diff --git a/app/PersonalAccessTokens/Actions/CreateAccessTokenAction.php b/app/PersonalAccessTokens/Actions/CreateAccessTokenAction.php new file mode 100644 index 0000000..0728dd8 --- /dev/null +++ b/app/PersonalAccessTokens/Actions/CreateAccessTokenAction.php @@ -0,0 +1,27 @@ +createAccessTokenQuery)(user: $user, tokenName: $tokenName); + } +} diff --git a/app/PersonalAccessTokens/Actions/DeleteAccessTokenAction.php b/app/PersonalAccessTokens/Actions/DeleteAccessTokenAction.php new file mode 100644 index 0000000..11d8f4a --- /dev/null +++ b/app/PersonalAccessTokens/Actions/DeleteAccessTokenAction.php @@ -0,0 +1,32 @@ +deleteAccessTokenByNameQuery)( tokenName: $tokenName, user: $user); + } +} diff --git a/app/PersonalAccessTokens/Enums/AccessTokenName.php b/app/PersonalAccessTokens/Enums/AccessTokenName.php new file mode 100644 index 0000000..94e2444 --- /dev/null +++ b/app/PersonalAccessTokens/Enums/AccessTokenName.php @@ -0,0 +1,8 @@ + EfficientUuid::class, + 'abilities' => 'json', + 'last_used_at' => 'datetime', + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'uuid', + 'name', + 'token', + 'abilities', + ]; +} diff --git a/app/PersonalAccessTokens/Queries/CreateAccessTokenQuery.php b/app/PersonalAccessTokens/Queries/CreateAccessTokenQuery.php new file mode 100644 index 0000000..4292bb9 --- /dev/null +++ b/app/PersonalAccessTokens/Queries/CreateAccessTokenQuery.php @@ -0,0 +1,21 @@ +createToken($tokenName)->plainTextToken; + } +} diff --git a/app/PersonalAccessTokens/Queries/DeleteAccessTokenByNameQuery.php b/app/PersonalAccessTokens/Queries/DeleteAccessTokenByNameQuery.php new file mode 100644 index 0000000..9c3c9b6 --- /dev/null +++ b/app/PersonalAccessTokens/Queries/DeleteAccessTokenByNameQuery.php @@ -0,0 +1,21 @@ +tokens()->where('name', $tokenName)->delete(); + } +} diff --git a/app/PersonalAccessTokens/Routes/api.php b/app/PersonalAccessTokens/Routes/api.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/app/PersonalAccessTokens/Routes/api.php @@ -0,0 +1 @@ + $this->faker->name(), - 'email' => $this->faker->unique()->safeEmail(), - 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'email' => $this->faker->unique()->freeEmail(), + 'email_verified_at' => \Illuminate\Support\Carbon::now(), + 'password' => bcrypt('password'), 'remember_token' => Str::random(10), ]; } @@ -36,7 +36,7 @@ public function definition() * * @return \Illuminate\Database\Eloquent\Factories\Factory */ - public function unverified() + public function unverified(): Factory { return $this->state(function (array $attributes) { return [ diff --git a/app/Users/Models/User.php b/app/Users/Models/User.php index d4df6e7..f4730a3 100644 --- a/app/Users/Models/User.php +++ b/app/Users/Models/User.php @@ -3,13 +3,67 @@ namespace DavorMinchorov\Users\Models; use Dyrynda\Database\Casts\EfficientUuid; +use Dyrynda\Database\Support\GeneratesUuid; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; +/** + * DavorMinchorov\Users\Models\User + * + * @property string $uuid + * @property string $name + * @property string $email + * @property \Illuminate\Support\Carbon|null $email_verified_at + * @property string $password + * @property string|null $remember_token + * @property string|null $access_token + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications + * @property-read int|null $notifications_count + * @property-read \Illuminate\Database\Eloquent\Collection|\Laravel\Sanctum\PersonalAccessToken[] $tokens + * @property-read int|null $tokens_count + * @method static \DavorMinchorov\Users\Factories\UserFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User query() + * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerifiedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUuid($value) + * @mixin \Eloquent + */ class User extends Authenticatable { - use HasFactory, Notifiable; + use HasFactory, Notifiable, GeneratesUuid, HasApiTokens; + + /** + * The primary key for the model. + * + * @var string + */ + protected $primaryKey = 'uuid'; + + /** + * The "type" of the primary key ID. + * + * @var string + */ + protected $keyType = 'string'; + + /** + * Indicates if the IDs are auto-incrementing. + * + * @var bool + */ + public $incrementing = false; /** * The attributes that are mass assignable. @@ -17,6 +71,7 @@ class User extends Authenticatable * @var array */ protected $fillable = [ + 'uuid', 'name', 'email', 'password', diff --git a/app/Users/Queries/GetUserByEmailQuery.php b/app/Users/Queries/GetUserByEmailQuery.php new file mode 100644 index 0000000..cef8c33 --- /dev/null +++ b/app/Users/Queries/GetUserByEmailQuery.php @@ -0,0 +1,33 @@ +user->newQuery()->where('email', $email)->first(); + + /** @var User $user */ + return $user ?? $this->user; + } +} diff --git a/app/Users/Routes/api.php b/app/Users/Routes/api.php index 8dc7270..b3d9bbc 100644 --- a/app/Users/Routes/api.php +++ b/app/Users/Routes/api.php @@ -1,9 +1 @@ router->get('/', function () { - return response()->json([ - 'version' => 1, - 'name' => 'Davor Minchorov API', - 'module' => 'Users', - ]); -}); diff --git a/composer.json b/composer.json index 4eb54e1..4713d53 100644 --- a/composer.json +++ b/composer.json @@ -10,12 +10,14 @@ "dyrynda/laravel-model-uuid": "^6.4", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", - "laravel/framework": "^8.54", + "laravel/framework": "^8.56", "laravel/octane": "^1.0", + "laravel/sanctum": "^2.11", "laravel/tinker": "^2.5", - "nwidart/laravel-modules": "^8.2" + "spatie/data-transfer-object": "^3.7" }, "require-dev": { + "barryvdh/laravel-ide-helper": "^2.10", "facade/ignition": "^2.5", "fakerphp/faker": "^1.9.1", "friendsofphp/php-cs-fixer": "^3.0", @@ -28,13 +30,13 @@ "autoload": { "psr-4": { "DavorMinchorov\\": "app/", - "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" } }, "autoload-dev": { "psr-4": { - "DavorMinchorov\\Framework\\Tests\\": "app/Framework/Tests" + "DavorMinchorov\\Framework\\Tests\\": "app/Framework/Tests", + "DavorMinchorov\\Authentication\\Tests\\": "app/Authentication/Tests" } }, "scripts": { diff --git a/composer.lock b/composer.lock index b5001fd..d6f2d5e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fcff35d2599911d584a38e61c32e7f5e", + "content-hash": "6c3dbadf06d67a21bdc4c0b7210c3a1d", "packages": [ { "name": "asm89/stack-cors", @@ -1396,6 +1396,70 @@ }, "time": "2021-08-10T17:31:47+00:00" }, + { + "name": "laravel/sanctum", + "version": "v2.11.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "b21e65cbe13896946986cb0868180cd69e1bd5d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/b21e65cbe13896946986cb0868180cd69e1bd5d3", + "reference": "b21e65cbe13896946986cb0868180cd69e1bd5d3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^6.9|^7.0|^8.0", + "illuminate/database": "^6.9|^7.0|^8.0", + "illuminate/support": "^6.9|^7.0|^8.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2021-06-15T15:56:21+00:00" + }, { "name": "laravel/tinker", "version": "v2.6.1", @@ -2200,87 +2264,6 @@ }, "time": "2021-07-21T10:44:31+00:00" }, - { - "name": "nwidart/laravel-modules", - "version": "8.2.0", - "source": { - "type": "git", - "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "6ade5ec19e81a0e4807834886a2c47509d069cb7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/6ade5ec19e81a0e4807834886a2c47509d069cb7", - "reference": "6ade5ec19e81a0e4807834886a2c47509d069cb7", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "laravel/framework": "^8.0", - "mockery/mockery": "~1.0", - "orchestra/testbench": "^6.2", - "phpstan/phpstan": "^0.12.14", - "phpunit/phpunit": "^8.5", - "spatie/phpunit-snapshot-assertions": "^2.1.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Nwidart\\Modules\\LaravelModulesServiceProvider" - ], - "aliases": { - "Module": "Nwidart\\Modules\\Facades\\Module" - } - }, - "branch-alias": { - "dev-master": "8.0-dev" - } - }, - "autoload": { - "psr-4": { - "Nwidart\\Modules\\": "src" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Widart", - "email": "n.widart@gmail.com", - "homepage": "https://nicolaswidart.com", - "role": "Developer" - } - ], - "description": "Laravel Module management", - "keywords": [ - "laravel", - "module", - "modules", - "nwidart", - "rad" - ], - "support": { - "issues": "https://github.com/nWidart/laravel-modules/issues", - "source": "https://github.com/nWidart/laravel-modules/tree/8.2.0" - }, - "funding": [ - { - "url": "https://github.com/nwidart", - "type": "github" - } - ], - "time": "2020-11-11T09:24:22+00:00" - }, { "name": "opis/closure", "version": "3.6.2", @@ -3069,6 +3052,72 @@ ], "time": "2021-08-11T01:06:55+00:00" }, + { + "name": "spatie/data-transfer-object", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/data-transfer-object.git", + "reference": "34289b121067206701c0e6efa7fb0f1fd3453764" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/data-transfer-object/zipball/34289b121067206701c0e6efa7fb0f1fd3453764", + "reference": "34289b121067206701c0e6efa7fb0f1fd3453764", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "illuminate/collections": "^8.36", + "jetbrains/phpstorm-attributes": "^1.0", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "phpstan/phpstan": "Take advantage of checkUninitializedProperties with \\Spatie\\DataTransferObject\\PHPstan\\PropertiesAreAlwaysInitializedExtension" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\DataTransferObject\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Data transfer objects with batteries included", + "homepage": "https://github.com/spatie/data-transfer-object", + "keywords": [ + "data-transfer-object", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/data-transfer-object/issues", + "source": "https://github.com/spatie/data-transfer-object/tree/3.7.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-08-26T09:53:42+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v6.2.7", @@ -5754,6 +5803,148 @@ } ], "packages-dev": [ + { + "name": "barryvdh/laravel-ide-helper", + "version": "v2.10.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/73b1012b927633a1b4cd623c2e6b1678e6faef08", + "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08", + "shasum": "" + }, + "require": { + "barryvdh/reflection-docblock": "^2.0.6", + "composer/composer": "^1.6 || ^2", + "doctrine/dbal": "^2.6 || ^3", + "ext-json": "*", + "illuminate/console": "^8", + "illuminate/filesystem": "^8", + "illuminate/support": "^8", + "nikic/php-parser": "^4.7", + "php": "^7.3 || ^8.0", + "phpdocumentor/type-resolver": "^1.1.0" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "friendsofphp/php-cs-fixer": "^2", + "illuminate/config": "^8", + "illuminate/view": "^8", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^6", + "phpunit/phpunit": "^8.5 || ^9", + "spatie/phpunit-snapshot-assertions": "^3 || ^4", + "vimeo/psalm": "^3.12" + }, + "suggest": { + "illuminate/events": "Required for automatic helper generation (^6|^7|^8)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.9-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\LaravelIdeHelper\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", + "keywords": [ + "autocomplete", + "codeintel", + "helper", + "ide", + "laravel", + "netbeans", + "phpdoc", + "phpstorm", + "sublime" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.10.0" + }, + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2021-04-09T06:17:55+00:00" + }, + { + "name": "barryvdh/reflection-docblock", + "version": "v2.0.6", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/ReflectionDocBlock.git", + "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/6b69015d83d3daf9004a71a89f26e27d27ef6a16", + "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0,<4.5" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Barryvdh": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.0.6" + }, + "time": "2018-12-13T10:34:14+00:00" + }, { "name": "composer/ca-bundle", "version": "1.2.10", @@ -5998,35 +6189,41 @@ "time": "2021-04-07T13:37:33+00:00" }, { - "name": "composer/semver", - "version": "3.2.5", + "name": "composer/package-versions-deprecated", + "version": "1.11.99.3", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "fff576ac850c045158a250e7e27666e146e78d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18", + "reference": "fff576ac850c045158a250e7e27666e146e78d18", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" }, "require-dev": { - "phpstan/phpstan": "^0.12.54", - "symfony/phpunit-bridge": "^4.2 || ^5" + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" }, - "type": "library", + "type": "composer-plugin", "extra": { + "class": "PackageVersions\\Installer", "branch-alias": { - "dev-main": "3.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "Composer\\Semver\\": "src" + "PackageVersions\\": "src/PackageVersions" } }, "notification-url": "https://packagist.org/downloads/", @@ -6035,16 +6232,83 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" }, { "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-08-17T13:49:14+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.5", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http://robbast.nl" @@ -6293,6 +6557,351 @@ }, "time": "2021-08-05T19:00:23+00:00" }, + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/8e0fde2b90e3f61361013d1e928621beeea07bc0", + "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.11.99", + "doctrine/cache": "^1.0|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2020.2", + "phpstan/phpstan": "0.12.81", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpunit/phpunit": "9.5.5", + "psalm/plugin-phpunit": "0.13.0", + "squizlabs/php_codesniffer": "3.6.0", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.6.4" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.1.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2021-06-19T17:59:55+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.0", diff --git a/config/app.php b/config/app.php index 6a00f7d..a646d8b 100644 --- a/config/app.php +++ b/config/app.php @@ -52,7 +52,7 @@ | */ - 'url' => env('APP_URL', 'http://api.davorminchorov.com'), + 'url' => env('APP_URL', 'https://api.davorminchorov.com'), 'asset_url' => env('ASSET_URL', null), @@ -170,12 +170,9 @@ /* * Application Service Providers... */ - DavorMinchorov\Framework\Providers\AppServiceProvider::class, - DavorMinchorov\Framework\Providers\AuthServiceProvider::class, - // DavorMinchorov\Framework\Providers\BroadcastServiceProvider::class, - DavorMinchorov\Framework\Providers\EventServiceProvider::class, DavorMinchorov\Framework\Providers\RouteServiceProvider::class, - + DavorMinchorov\Framework\Providers\ModelFactoryServiceProvider::class, + DavorMinchorov\Framework\Providers\SanctumServiceProvider::class, ], /* diff --git a/config/auth.php b/config/auth.php index 4f406d8..61307da 100644 --- a/config/auth.php +++ b/config/auth.php @@ -68,7 +68,7 @@ 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => DavorMinchorov\Users\Models\User::class, + 'model' => DavorMinchorov\PersonalAccessTokens\Models\User::class, ], // 'users' => [ diff --git a/config/cors.php b/config/cors.php index 8a39e6d..4941f4c 100644 --- a/config/cors.php +++ b/config/cors.php @@ -15,7 +15,10 @@ | */ - 'paths' => ['api/*', 'sanctum/csrf-cookie'], + 'paths' => [ + 'api/*', + 'sanctum/csrf-cookie', + ], 'allowed_methods' => ['*'], diff --git a/config/ide-helper.php b/config/ide-helper.php new file mode 100644 index 0000000..4a62954 --- /dev/null +++ b/config/ide-helper.php @@ -0,0 +1,308 @@ + '_ide_helper.php', + + /* + |-------------------------------------------------------------------------- + | Where to write the PhpStorm specific meta file + |-------------------------------------------------------------------------- + | + | PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary + | files in it, should you need additional files for your project; e.g. + | `.phpstorm.meta.php/laravel_ide_Helper.php'. + | + */ + 'meta_filename' => '.phpstorm.meta.php', + + /* + |-------------------------------------------------------------------------- + | Fluent helpers + |-------------------------------------------------------------------------- + | + | Set to true to generate commonly used Fluent methods + | + */ + + 'include_fluent' => false, + + /* + |-------------------------------------------------------------------------- + | Factory Builders + |-------------------------------------------------------------------------- + | + | Set to true to generate factory generators for better factory() + | method auto-completion. + | + | Deprecated for Laravel 8 or latest. + | + */ + + 'include_factory_builders' => false, + + /* + |-------------------------------------------------------------------------- + | Write Model Magic methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write magic methods of model + | + */ + + 'write_model_magic_where' => true, + + /* + |-------------------------------------------------------------------------- + | Write Model External Eloquent Builder methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write external eloquent builder methods + | + */ + + 'write_model_external_builder_methods' => true, + + /* + |-------------------------------------------------------------------------- + | Write Model relation count properties + |-------------------------------------------------------------------------- + | + | Set to false to disable writing of relation count properties to model DocBlocks. + | + */ + + 'write_model_relation_count_properties' => true, + + /* + |-------------------------------------------------------------------------- + | Write Eloquent Model Mixins + |-------------------------------------------------------------------------- + | + | This will add the necessary DocBlock mixins to the model class + | contained in the Laravel Framework. This helps the IDE with + | auto-completion. + | + | Please be aware that this setting changes a file within the /vendor directory. + | + */ + + 'write_eloquent_model_mixins' => false, + + /* + |-------------------------------------------------------------------------- + | Helper files to include + |-------------------------------------------------------------------------- + | + | Include helper files. By default not included, but can be toggled with the + | -- helpers (-H) option. Extra helper files can be included. + | + */ + + 'include_helpers' => false, + + 'helper_files' => [ + base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', + ], + + /* + |-------------------------------------------------------------------------- + | Model locations to include + |-------------------------------------------------------------------------- + | + | Define in which directories the ide-helper:models command should look + | for models. + | + | glob patterns are supported to easier reach models in sub-directories, + | e.g. `app/Services/* /Models` (without the space) + | + */ + + 'model_locations' => [ + 'app', + ], + + /* + |-------------------------------------------------------------------------- + | Models to ignore + |-------------------------------------------------------------------------- + | + | Define which models should be ignored. + | + */ + + 'ignored_models' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Models hooks + |-------------------------------------------------------------------------- + | + | Define which hook classes you want to run for models to add custom information + | + | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. + | + */ + + 'model_hooks' => [ + // App\Support\IdeHelper\MyModelHook::class + ], + + /* + |-------------------------------------------------------------------------- + | Extra classes + |-------------------------------------------------------------------------- + | + | These implementations are not really extended, but called with magic functions + | + */ + + 'extra' => [ + 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], + 'Session' => ['Illuminate\Session\Store'], + ], + + 'magic' => [], + + /* + |-------------------------------------------------------------------------- + | Interface implementations + |-------------------------------------------------------------------------- + | + | These interfaces will be replaced with the implementing class. Some interfaces + | are detected by the helpers, others can be listed below. + | + */ + + 'interfaces' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Support for custom DB types + |-------------------------------------------------------------------------- + | + | This setting allow you to map any custom database type (that you may have + | created using CREATE TYPE statement or imported using database plugin + | / extension to a Doctrine type. + | + | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are: + | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql' + | + | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant + | + | The value of the array is an array of type mappings. Key is the name of the custom type, + | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in + | our case it is 'json_array'. Doctrine types are listed here: + | http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html + | + | So to support jsonb in your models when working with Postgres, just add the following entry to the array below: + | + | "postgresql" => array( + | "jsonb" => "json_array", + | ), + | + */ + 'custom_db_types' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Support for camel cased models + |-------------------------------------------------------------------------- + | + | There are some Laravel packages (such as Eloquence) that allow for accessing + | Eloquent model properties via camel case, instead of snake case. + | + | Enabling this option will support these packages by saving all model + | properties as camel case, instead of snake case. + | + | For example, normally you would see this: + | + | * @property \Illuminate\Support\Carbon $created_at + | * @property \Illuminate\Support\Carbon $updated_at + | + | With this enabled, the properties will be this: + | + | * @property \Illuminate\Support\Carbon $createdAt + | * @property \Illuminate\Support\Carbon $updatedAt + | + | Note, it is currently an all-or-nothing option. + | + */ + 'model_camel_case_properties' => false, + + /* + |-------------------------------------------------------------------------- + | Property Casts + |-------------------------------------------------------------------------- + | + | Cast the given "real type" to the given "type". + | + */ + 'type_overrides' => [ + 'integer' => 'int', + 'boolean' => 'bool', + ], + + /* + |-------------------------------------------------------------------------- + | Include DocBlocks from classes + |-------------------------------------------------------------------------- + | + | Include DocBlocks from classes to allow additional code inspection for + | magic methods and properties. + | + */ + 'include_class_docblocks' => false, + + /* + |-------------------------------------------------------------------------- + | Force FQN usage + |-------------------------------------------------------------------------- + | + | Use the fully qualified (class) name in docBlock, + | event if class exists in a given file + | or there is an import (use className) of a given class + | + */ + 'force_fqn' => false, + + /* + |-------------------------------------------------------------------------- + | Additional relation types + |-------------------------------------------------------------------------- + | + | Sometimes it's needed to create custom relation types. The key of the array + | is the Relationship Method name. The value of the array is the canonical class + | name of the Relationship, e.g. `'relationName' => RelationShipClass::class`. + | + */ + 'additional_relation_types' => [], + + /* + |-------------------------------------------------------------------------- + | Run artisan commands after migrations to generate model helpers + |-------------------------------------------------------------------------- + | + | The specified commands should run after migrations are finished running. + | + */ + 'post_migrate' => [ + // 'ide-helper:models --nowrite', + ], + +]; diff --git a/config/modules.php b/config/modules.php deleted file mode 100644 index d04ad75..0000000 --- a/config/modules.php +++ /dev/null @@ -1,310 +0,0 @@ - 'DavorMinchorov', - - /* - |-------------------------------------------------------------------------- - | Module Stubs - |-------------------------------------------------------------------------- - | - | Default module stubs. - | - */ - - 'stubs' => [ - 'enabled' => false, - 'path' => base_path().'/vendor/nwidart/laravel-modules/src/Commands/stubs', - 'files' => [ - 'routes/web' => 'Routes/web.php', - 'routes/api' => 'Routes/api.php', - 'views/index' => 'Resources/views/index.blade.php', - 'views/master' => 'Resources/views/layouts/master.blade.php', - 'scaffold/config' => 'Config/config.php', - 'composer' => 'composer.json', - ], - 'replacements' => [ - 'routes/web' => ['LOWER_NAME', 'STUDLY_NAME'], - 'routes/api' => ['LOWER_NAME'], - 'webpack' => ['LOWER_NAME'], - 'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'PROVIDER_NAMESPACE'], - 'views/index' => ['LOWER_NAME'], - 'views/master' => ['LOWER_NAME', 'STUDLY_NAME'], - 'scaffold/config' => ['STUDLY_NAME'], - 'composer' => [ - 'LOWER_NAME', - 'STUDLY_NAME', - 'VENDOR', - 'AUTHOR_NAME', - 'AUTHOR_EMAIL', - 'MODULE_NAMESPACE', - 'PROVIDER_NAMESPACE', - ], - ], - 'gitkeep' => true, - ], - 'paths' => [ - /* - |-------------------------------------------------------------------------- - | Modules path - |-------------------------------------------------------------------------- - | - | This path used for save the generated module. This path also will be added - | automatically to list of scanned folders. - | - */ - - 'modules' => app_path(), - /* - |-------------------------------------------------------------------------- - | Modules assets path - |-------------------------------------------------------------------------- - | - | Here you may update the modules assets path. - | - */ - - 'assets' => public_path('modules'), - /* - |-------------------------------------------------------------------------- - | The migrations path - |-------------------------------------------------------------------------- - | - | Where you run 'module:publish-migration' command, where do you publish the - | the migration files? - | - */ - - 'migration' => base_path('database/migrations'), - /* - |-------------------------------------------------------------------------- - | Generator path - |-------------------------------------------------------------------------- - | Customise the paths where the folders will be generated. - | Set the generate key to false to not generate that folder - */ - 'generator' => [ - 'config' => ['path' => 'Config', 'generate' => true], - 'command' => ['path' => 'Console', 'generate' => true], - 'migration' => ['path' => 'Database/Migrations', 'generate' => true], - 'seeder' => ['path' => 'Database/Seeders', 'generate' => true], - 'factory' => ['path' => 'Database/factories', 'generate' => true], - 'model' => ['path' => 'Entities', 'generate' => true], - 'routes' => ['path' => 'Routes', 'generate' => true], - 'controller' => ['path' => 'Api/V1/Controllers', 'generate' => true], - 'filter' => ['path' => 'Api/V1/Middleware', 'generate' => true], - 'request' => ['path' => 'Api/V1/Requests', 'generate' => true], - 'provider' => ['path' => 'Providers', 'generate' => true], - 'assets' => ['path' => 'Resources/assets', 'generate' => true], - 'lang' => ['path' => 'Resources/lang', 'generate' => true], - 'views' => ['path' => 'Resources/views', 'generate' => true], - 'test' => ['path' => 'Tests/Unit', 'generate' => true], - 'test-feature' => ['path' => 'Tests/Feature', 'generate' => true], - 'repository' => ['path' => 'Repositories', 'generate' => false], - 'event' => ['path' => 'Events', 'generate' => false], - 'listener' => ['path' => 'Listeners', 'generate' => false], - 'policies' => ['path' => 'Policies', 'generate' => false], - 'rules' => ['path' => 'Rules', 'generate' => false], - 'jobs' => ['path' => 'Jobs', 'generate' => false], - 'emails' => ['path' => 'Emails', 'generate' => false], - 'notifications' => ['path' => 'Notifications', 'generate' => false], - 'resource' => ['path' => 'Transformers', 'generate' => false], - 'component-view' => ['path' => 'Resources/views/components', 'generate' => false], - 'component-class' => ['path' => 'View/Component', 'generate' => false], - ], - ], - - /* - |-------------------------------------------------------------------------- - | Package commands - |-------------------------------------------------------------------------- - | - | Here you can define which commands will be visible and used in your - | application. If for example you don't use some of the commands provided - | you can simply comment them out. - | - */ - 'commands' => [ - CommandMakeCommand::class, - ControllerMakeCommand::class, - DisableCommand::class, - DumpCommand::class, - EnableCommand::class, - EventMakeCommand::class, - JobMakeCommand::class, - ListenerMakeCommand::class, - MailMakeCommand::class, - MiddlewareMakeCommand::class, - NotificationMakeCommand::class, - ProviderMakeCommand::class, - RouteProviderMakeCommand::class, - InstallCommand::class, - ListCommand::class, - ModuleDeleteCommand::class, - ModuleMakeCommand::class, - FactoryMakeCommand::class, - PolicyMakeCommand::class, - RequestMakeCommand::class, - RuleMakeCommand::class, - MigrateCommand::class, - MigrateRefreshCommand::class, - MigrateResetCommand::class, - MigrateRollbackCommand::class, - MigrateStatusCommand::class, - MigrationMakeCommand::class, - ModelMakeCommand::class, - PublishCommand::class, - PublishConfigurationCommand::class, - PublishMigrationCommand::class, - PublishTranslationCommand::class, - SeedCommand::class, - SeedMakeCommand::class, - SetupCommand::class, - UnUseCommand::class, - UpdateCommand::class, - UseCommand::class, - ResourceMakeCommand::class, - TestMakeCommand::class, - LaravelModulesV6Migrator::class, - ], - - /* - |-------------------------------------------------------------------------- - | Scan Path - |-------------------------------------------------------------------------- - | - | Here you define which folder will be scanned. By default will scan vendor - | directory. This is useful if you host the package in packagist website. - | - */ - - 'scan' => [ - 'enabled' => false, - 'paths' => [ - base_path('vendor/*/*'), - ], - ], - /* - |-------------------------------------------------------------------------- - | Composer File Template - |-------------------------------------------------------------------------- - | - | Here is the config for composer.json file, generated by this package - | - */ - - 'composer' => [ - 'vendor' => 'davorminchorov', - 'author' => [ - 'name' => 'Davor Minchorov', - 'email' => 'davorminchorov@gmail.com', - ], - ], - - 'composer-output' => false, - - /* - |-------------------------------------------------------------------------- - | Caching - |-------------------------------------------------------------------------- - | - | Here is the config for setting up caching feature. - | - */ - 'cache' => [ - 'enabled' => true, - 'key' => 'laravel-modules', - 'lifetime' => 60, - ], - /* - |-------------------------------------------------------------------------- - | Choose what laravel-modules will register as custom namespaces. - | Setting one to false will require you to register that part - | in your own Service Provider class. - |-------------------------------------------------------------------------- - */ - 'register' => [ - 'translations' => true, - /* - * load files on boot or register method - * - * Note: boot not compatible with asgardcms - * - * @example boot|register - */ - 'files' => 'register', - ], - - /* - |-------------------------------------------------------------------------- - | Activators - |-------------------------------------------------------------------------- - | - | You can define new types of activators here, file, database etc. The only - | required parameter is 'class'. - | The file activator will store the activation status in storage/installed_modules - */ - 'activators' => [ - 'file' => [ - 'class' => FileActivator::class, - 'statuses-file' => base_path('modules_statuses.json'), - 'cache-key' => 'activator.installed', - 'cache-lifetime' => 604800, - ], - ], - - 'activator' => 'file', -]; diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000..7477b7a --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,54 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' + ))), + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => VerifyCsrfToken::class, + 'encrypt_cookies' => EncryptCookies::class, + ], + +]; diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 621a24e..d88825d 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -11,10 +11,10 @@ class CreateUsersTable extends Migration * * @return void */ - public function up() + public function up(): void { Schema::create('users', function (Blueprint $table) { - $table->id(); + $table->efficientUuid('uuid'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); @@ -29,7 +29,7 @@ public function up() * * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('users'); } diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 0000000..d555709 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,36 @@ +efficientUuid('uuid'); + $table->uuidMorphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 23e1bf6..d1744cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,9 @@ version: '3' services: api.davorminchorov.test: + container_name: davorminchorov_api + extra_hosts: + - 'host.docker.internal:host-gateway' build: context: ./docker/8.0 dockerfile: Dockerfile @@ -13,6 +16,7 @@ services: environment: WWWUSER: '${WWWUSER}' LARAVEL_SAIL: 1 + PHP_IDE_CONFIG: serverName=Docker XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' volumes: @@ -21,7 +25,9 @@ services: - sail depends_on: - mysql + - mysql_testing mysql: + container_name: davorminchorov_mysql image: 'mariadb:latest' ports: - '${FORWARD_DB_PORT:-3306}:3306' @@ -39,9 +45,31 @@ services: test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"] retries: 3 timeout: 5s + + mysql_testing: + container_name: davorminchorov_mysql_testing + image: 'mariadb:latest' + ports: + - '${FORWARD_DB_PORT:-3307}:3307' + environment: + MYSQL_ROOT_PASSWORD: '${DB_TEST_PASSWORD}' + MYSQL_DATABASE: '${DB_TEST_DATABASE}' + MYSQL_USER: '${DB_TEST_USERNAME}' + MYSQL_PASSWORD: '${DB_TEST_PASSWORD}' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + volumes: + - 'sailmysqltesting:/var/lib/mysql' + networks: + - sail + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-p${DB_TEST_PASSWORD}" ] + retries: 3 + timeout: 5s networks: sail: driver: bridge volumes: sailmysql: driver: local + sailmysqltesting: + driver: local diff --git a/docker/8.0/Dockerfile b/docker/8.0/Dockerfile index 0cbd01f..3d406df 100644 --- a/docker/8.0/Dockerfile +++ b/docker/8.0/Dockerfile @@ -3,6 +3,8 @@ FROM ubuntu:21.04 LABEL maintainer="Taylor Otwell" ARG WWWGROUP +ARG XDEBUG +ARG XDEBUG_PORT WORKDIR /var/www/html diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 65d789b..5b810ef 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,10 +7,9 @@ parameters: - app # The level 8 is the highest level - level: 5 + level: 8 ignoreErrors: - - '#Variable \$this might not be defined#' excludePaths: - ./*/*/FileToBeExcluded.php diff --git a/phpunit.xml b/phpunit.xml index 830a847..78306b8 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,8 +21,11 @@ - - + + + + + diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php new file mode 100644 index 0000000..e5506df --- /dev/null +++ b/resources/lang/en/auth.php @@ -0,0 +1,19 @@ + 'These credentials do not match our records.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/resources/lang/en/pagination.php b/resources/lang/en/pagination.php new file mode 100644 index 0000000..d481411 --- /dev/null +++ b/resources/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/resources/lang/en/passwords.php b/resources/lang/en/passwords.php new file mode 100644 index 0000000..e5544d2 --- /dev/null +++ b/resources/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Passwords must be at least six characters and match the confirmation.', + 'reset' => 'Your password has been reset!', + 'sent' => 'We have e-mailed your password reset link!', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that e-mail address.", + +]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php new file mode 100644 index 0000000..e1d879f --- /dev/null +++ b/resources/lang/en/validation.php @@ -0,0 +1,150 @@ + 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ + 'numeric' => 'The :attribute must be between :min and :max.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'ipv4' => 'The :attribute must be a valid IPv4 address.', + 'ipv6' => 'The :attribute must be a valid IPv6 address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], + 'max' => [ + 'numeric' => 'The :attribute may not be greater than :max.', + 'file' => 'The :attribute may not be greater than :max kilobytes.', + 'string' => 'The :attribute may not be greater than :max characters.', + 'array' => 'The :attribute may not have more than :max items.', + ], + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ + 'numeric' => 'The :attribute must be at least :min.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', + ], + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'numeric' => 'The :attribute must be :size.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', + ], + 'starts_with' => 'The :attribute must start with one of the following: :values', + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute format is invalid.', + 'uuid' => 'The :attribute must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +];