Update app to match latest Laravel defaults and best practices

This commit is contained in:
Jonathan Reinink 2021-05-10 15:27:31 -04:00
parent ee2716ee4c
commit 4e5c3c938f
40 changed files with 2987 additions and 2842 deletions

View File

@ -9,13 +9,15 @@ return PhpCsFixer\Config::create()
'@PHP70Migration' => true, '@PHP70Migration' => true,
'@PHP71Migration' => true, '@PHP71Migration' => true,
'@PSR2' => true, '@PSR2' => true,
// '@Symfony' => true, '@Symfony' => true,
'array_syntax' => ['syntax' => 'short'], 'array_syntax' => ['syntax' => 'short'],
'increment_style' => ['style' => 'post'], 'increment_style' => ['style' => 'post'],
'no_multiline_whitespace_before_semicolons' => true, 'no_multiline_whitespace_before_semicolons' => true,
'not_operator_with_successor_space' => true, 'not_operator_with_successor_space' => true,
'ordered_imports' => ['sortAlgorithm' => 'alpha'], 'ordered_imports' => ['sortAlgorithm' => 'alpha'],
'php_unit_method_casing' => ['case' => 'snake_case'],
'semicolon_after_instruction' => false, 'semicolon_after_instruction' => false,
'single_line_throw' => false,
'strict_comparison' => true, 'strict_comparison' => true,
'yoda_style' => false, 'yoda_style' => false,
]); ]);

13
.styleci.yml Normal file
View File

@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- no_unused_imports
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View File

@ -3,6 +3,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@ -21,6 +22,7 @@ class Handler extends ExceptionHandler
* @var array * @var array
*/ */
protected $dontFlash = [ protected $dontFlash = [
'current_password',
'password', 'password',
'password_confirmation', 'password_confirmation',
]; ];
@ -32,6 +34,8 @@ class Handler extends ExceptionHandler
*/ */
public function register() public function register()
{ {
// $this->reportable(function (Throwable $e) {
//
});
} }
} }

View File

@ -19,18 +19,16 @@ class ContactsController extends Controller
->with('organization') ->with('organization')
->orderByName() ->orderByName()
->filter(Request::only('search', 'trashed')) ->filter(Request::only('search', 'trashed'))
->paginate() ->paginate(10)
->withQueryString() ->withQueryString()
->through(function ($contact) { ->through(fn ($contact) => [
return [ 'id' => $contact->id,
'id' => $contact->id, 'name' => $contact->name,
'name' => $contact->name, 'phone' => $contact->phone,
'phone' => $contact->phone, 'city' => $contact->city,
'city' => $contact->city, 'deleted_at' => $contact->deleted_at,
'deleted_at' => $contact->deleted_at, 'organization' => $contact->organization ? $contact->organization->only('name') : null,
'organization' => $contact->organization ? $contact->organization->only('name') : null, ]),
];
}),
]); ]);
} }
@ -99,9 +97,10 @@ class ContactsController extends Controller
Request::validate([ Request::validate([
'first_name' => ['required', 'max:50'], 'first_name' => ['required', 'max:50'],
'last_name' => ['required', 'max:50'], 'last_name' => ['required', 'max:50'],
'organization_id' => ['nullable', Rule::exists('organizations', 'id')->where(function ($query) { 'organization_id' => [
$query->where('account_id', Auth::user()->account_id); 'nullable',
})], Rule::exists('organizations', 'id')->where(fn ($query) => $query->where('account_id', Auth::user()->account_id)),
],
'email' => ['nullable', 'max:50', 'email'], 'email' => ['nullable', 'max:50', 'email'],
'phone' => ['nullable', 'max:50'], 'phone' => ['nullable', 'max:50'],
'address' => ['nullable', 'max:150'], 'address' => ['nullable', 'max:150'],

View File

@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController class Controller extends BaseController
{ {
use AuthorizesRequests, DispatchesJobs, ValidatesRequests; use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
} }

View File

@ -17,17 +17,15 @@ class OrganizationsController extends Controller
'organizations' => Auth::user()->account->organizations() 'organizations' => Auth::user()->account->organizations()
->orderBy('name') ->orderBy('name')
->filter(Request::only('search', 'trashed')) ->filter(Request::only('search', 'trashed'))
->paginate() ->paginate(10)
->withQueryString() ->withQueryString()
->through(function ($organization) { ->through(fn ($organization) => [
return [ 'id' => $organization->id,
'id' => $organization->id, 'name' => $organization->name,
'name' => $organization->name, 'phone' => $organization->phone,
'phone' => $organization->phone, 'city' => $organization->city,
'city' => $organization->city, 'deleted_at' => $organization->deleted_at,
'deleted_at' => $organization->deleted_at, ]),
];
}),
]); ]);
} }

View File

@ -20,16 +20,14 @@ class UsersController extends Controller
->orderByName() ->orderByName()
->filter(Request::only('search', 'role', 'trashed')) ->filter(Request::only('search', 'role', 'trashed'))
->get() ->get()
->transform(function ($user) { ->transform(fn ($user) => [
return [ 'id' => $user->id,
'id' => $user->id, 'name' => $user->name,
'name' => $user->name, 'email' => $user->email,
'email' => $user->email, 'owner' => $user->owner,
'owner' => $user->owner, 'photo' => $user->photoUrl(['w' => 40, 'h' => 40, 'fit' => 'crop']),
'photo' => $user->photoUrl(['w' => 40, 'h' => 40, 'fit' => 'crop']), 'deleted_at' => $user->deleted_at,
'deleted_at' => $user->deleted_at, ]),
];
}),
]); ]);
} }

View File

@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Closure; use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated class RedirectIfAuthenticated
@ -13,10 +14,10 @@ class RedirectIfAuthenticated
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @param string[]|null ...$guards * @param string|null ...$guards
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next, ...$guards) public function handle(Request $request, Closure $next, ...$guards)
{ {
$guards = empty($guards) ? [null] : $guards; $guards = empty($guards) ? [null] : $guards;

View File

@ -12,6 +12,7 @@ class TrimStrings extends Middleware
* @var array * @var array
*/ */
protected $except = [ protected $except = [
'current_password',
'password', 'password',
'password_confirmation', 'password_confirmation',
]; ];

View File

@ -10,7 +10,7 @@ class TrustProxies extends Middleware
/** /**
* The trusted proxies for this application. * The trusted proxies for this application.
* *
* @var array * @var array|string|null
*/ */
protected $proxies = '**'; protected $proxies = '**';
@ -19,5 +19,5 @@ class TrustProxies extends Middleware
* *
* @var int * @var int
*/ */
protected $headers = Request::HEADER_X_FORWARDED_AWS_ELB; protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB;
} }

View File

@ -2,6 +2,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Account extends Model class Account extends Model
{ {
public function users() public function users()

View File

@ -2,10 +2,13 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Contact extends Model class Contact extends Model
{ {
use HasFactory;
use SoftDeletes; use SoftDeletes;
public function organization() public function organization()

View File

@ -1,20 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
abstract class Model extends Eloquent
{
protected $guarded = [];
protected $perPage = 10;
public function resolveRouteBinding($value, $field = null)
{
return in_array(SoftDeletes::class, class_uses($this))
? $this->where($this->getRouteKeyName(), $value)->withTrashed()->first()
: parent::resolveRouteBinding($value);
}
}

View File

@ -2,10 +2,13 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Organization extends Model class Organization extends Model
{ {
use HasFactory;
use SoftDeletes; use SoftDeletes;
public function contacts() public function contacts()

View File

@ -2,22 +2,24 @@
namespace App\Models; namespace App\Models;
use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use League\Glide\Server; use League\Glide\Server;
class User extends Model implements AuthenticatableContract, AuthorizableContract class User extends Authenticatable
{ {
use SoftDeletes, Authenticatable, Authorizable; use HasFactory;
use Notifiable;
use SoftDeletes;
protected $casts = [ protected $casts = [
'owner' => 'boolean', 'owner' => 'boolean',
'email_verified_at' => 'datetime',
]; ];
public function account() public function account()

View File

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use League\Glide\Server; use League\Glide\Server;
@ -15,6 +16,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register() public function register()
{ {
Model::unguard();
$this->app->bind(Server::class, function ($app) { $this->app->bind(Server::class, function ($app) {
return Server::create([ return Server::create([
'source' => Storage::getDriver(), 'source' => Storage::getDriver(),

View File

@ -13,7 +13,7 @@ class AuthServiceProvider extends ServiceProvider
* @var array * @var array
*/ */
protected $policies = [ protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy', // 'App\Models\Model' => 'App\Policies\ModelPolicy',
]; ];
/** /**

View File

@ -19,6 +19,15 @@ class RouteServiceProvider extends ServiceProvider
*/ */
public const HOME = '/'; public const HOME = '/';
/**
* The controller namespace for the application.
*
* When present, controller route declarations will automatically be prefixed with this namespace.
*
* @var string|null
*/
// protected $namespace = 'App\\Http\\Controllers';
/** /**
* Define your route model bindings, pattern filters, etc. * Define your route model bindings, pattern filters, etc.
* *
@ -29,12 +38,14 @@ class RouteServiceProvider extends ServiceProvider
$this->configureRateLimiting(); $this->configureRateLimiting();
$this->routes(function () { $this->routes(function () {
Route::middleware('web')
->group(base_path('routes/web.php'));
Route::prefix('api') Route::prefix('api')
->middleware('api') ->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php')); ->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}); });
} }
@ -46,7 +57,7 @@ class RouteServiceProvider extends ServiceProvider
protected function configureRateLimiting() protected function configureRateLimiting()
{ {
RateLimiter::for('api', function (Request $request) { RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60); return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
}); });
} }
} }

0
artisan Executable file → Normal file
View File

View File

@ -2,62 +2,43 @@
"name": "laravel/laravel", "name": "laravel/laravel",
"type": "project", "type": "project",
"description": "The Laravel Framework.", "description": "The Laravel Framework.",
"keywords": [ "keywords": ["framework", "laravel"],
"framework",
"laravel"
],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.3|^8.0", "php": "^7.3|^8.0",
"ext-exif": "*", "ext-exif": "*",
"ext-gd": "*", "ext-gd": "*",
"facade/ignition": "^2.3.6", "fideloper/proxy": "^4.4",
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"fzaninotto/faker": "^1.9.1",
"guzzlehttp/guzzle": "^7.0.1", "guzzlehttp/guzzle": "^7.0.1",
"inertiajs/inertia-laravel": "^0.3", "inertiajs/inertia-laravel": "^0.4",
"laravel/framework": "^8.0", "laravel/framework": "^8.40",
"laravel/legacy-factories": "^1.0", "laravel/tinker": "^2.5",
"laravel/tinker": "^2.0",
"laravel/ui": "^2.0", "laravel/ui": "^2.0",
"league/glide": "2.0.x-dev", "league/glide": "2.0.x-dev",
"mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.3",
"tightenco/ziggy": "^0.8.0" "tightenco/ziggy": "^0.8.0"
}, },
"config": { "require-dev": {
"optimize-autoloader": true, "facade/ignition": "^2.5",
"preferred-install": "dist", "fakerphp/faker": "^1.9.1",
"sort-packages": true "laravel/sail": "^1.0.1",
}, "mockery/mockery": "^1.4.2",
"extra": { "nunomaduro/collision": "^5.0",
"laravel": { "phpunit/phpunit": "^9.3.3"
"dont-discover": []
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"App\\": "app/" "App\\": "app/",
}, "Database\\Factories\\": "database/factories/",
"classmap": [ "Database\\Seeders\\": "database/seeders/"
"database/seeds", }
"database/factories"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Tests\\": "tests/" "Tests\\": "tests/"
} }
}, },
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": { "scripts": {
"compile": [
"npm run prod",
"@php artisan migrate:fresh --seed"
],
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi" "@php artisan package:discover --ansi"
@ -67,6 +48,21 @@
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi" "@php artisan key:generate --ansi"
],
"compile": [
"@php artisan migrate:fresh --seed"
] ]
} },
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
} }

5011
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -201,6 +201,7 @@ return [
'Config' => Illuminate\Support\Facades\Config::class, 'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class, 'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class, 'Crypt' => Illuminate\Support\Facades\Crypt::class,
'Date' => Illuminate\Support\Facades\Date::class,
'DB' => Illuminate\Support\Facades\DB::class, 'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class, 'Event' => Illuminate\Support\Facades\Event::class,
@ -215,7 +216,7 @@ return [
'Password' => Illuminate\Support\Facades\Password::class, 'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class, 'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class, 'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class, // 'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class, 'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class, 'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class, 'Route' => Illuminate\Support\Facades\Route::class,

View File

@ -11,7 +11,7 @@ return [
| framework when an event needs to be broadcast. You may set this to | framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below. | any of the connections defined in the "connections" array below.
| |
| Supported: "pusher", "redis", "log", "null" | Supported: "pusher", "ably", "redis", "log", "null"
| |
*/ */
@ -41,6 +41,11 @@ return [
], ],
], ],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'default', 'connection' => 'default',

View File

@ -13,9 +13,6 @@ return [
| using this caching library. This connection is used when another is | using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function. | not explicitly specified when executing a given caching function.
| |
| Supported: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb"
|
*/ */
'default' => env('CACHE_DRIVER', 'file'), 'default' => env('CACHE_DRIVER', 'file'),
@ -29,6 +26,9 @@ return [
| well as their drivers. You may even define multiple stores for the | well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches. | same cache driver to group types of items stored in your caches.
| |
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "null"
|
*/ */
'stores' => [ 'stores' => [
@ -46,6 +46,7 @@ return [
'driver' => 'database', 'driver' => 'database',
'table' => 'cache', 'table' => 'cache',
'connection' => null, 'connection' => null,
'lock_connection' => null,
], ],
'file' => [ 'file' => [
@ -75,6 +76,7 @@ return [
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'cache', 'connection' => 'cache',
'lock_connection' => 'default',
], ],
'dynamodb' => [ 'dynamodb' => [

View File

@ -15,7 +15,7 @@ return [
| |
*/ */
'paths' => ['api/*'], 'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'], 'allowed_methods' => ['*'],

View File

@ -15,19 +15,6 @@ return [
'default' => env('FILESYSTEM_DRIVER', 'local'), 'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Default Cloud Filesystem Disk
|--------------------------------------------------------------------------
|
| Many applications store files both locally and in the cloud. For this
| reason, you may specify a default "cloud" driver here. This driver
| will be bound as the Cloud disk implementation in the container.
|
*/
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Filesystem Disks | Filesystem Disks

View File

@ -44,13 +44,13 @@ return [
'single' => [ 'single' => [
'driver' => 'single', 'driver' => 'single',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
], ],
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'days' => 14, 'days' => 14,
], ],
@ -59,12 +59,12 @@ return [
'url' => env('LOG_SLACK_WEBHOOK_URL'), 'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log', 'username' => 'Laravel Log',
'emoji' => ':boom:', 'emoji' => ':boom:',
'level' => 'critical', 'level' => env('LOG_LEVEL', 'critical'),
], ],
'papertrail' => [ 'papertrail' => [
'driver' => 'monolog', 'driver' => 'monolog',
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class, 'handler' => SyslogUdpHandler::class,
'handler_with' => [ 'handler_with' => [
'host' => env('PAPERTRAIL_URL'), 'host' => env('PAPERTRAIL_URL'),
@ -74,6 +74,7 @@ return [
'stderr' => [ 'stderr' => [
'driver' => 'monolog', 'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class, 'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'), 'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [ 'with' => [
@ -83,12 +84,12 @@ return [
'syslog' => [ 'syslog' => [
'driver' => 'syslog', 'driver' => 'syslog',
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
], ],
'errorlog' => [ 'errorlog' => [
'driver' => 'errorlog', 'driver' => 'errorlog',
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
], ],
'null' => [ 'null' => [

View File

@ -39,6 +39,7 @@ return [
'table' => 'jobs', 'table' => 'jobs',
'queue' => 'default', 'queue' => 'default',
'retry_after' => 90, 'retry_after' => 90,
'after_commit' => false,
], ],
'beanstalkd' => [ 'beanstalkd' => [
@ -47,6 +48,7 @@ return [
'queue' => 'default', 'queue' => 'default',
'retry_after' => 90, 'retry_after' => 90,
'block_for' => 0, 'block_for' => 0,
'after_commit' => false,
], ],
'sqs' => [ 'sqs' => [
@ -54,9 +56,10 @@ return [
'key' => env('AWS_ACCESS_KEY_ID'), 'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'), 'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'your-queue-name'), 'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'), 'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
], ],
'redis' => [ 'redis' => [
@ -65,6 +68,7 @@ return [
'queue' => env('REDIS_QUEUE', 'default'), 'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90, 'retry_after' => 90,
'block_for' => null, 'block_for' => null,
'after_commit' => false,
], ],
], ],

View File

@ -1,17 +1,36 @@
<?php <?php
use Faker\Generator as Faker; namespace Database\Factories;
$factory->define(App\Models\Contact::class, function (Faker $faker) { use App\Models\Contact;
return [ use Illuminate\Database\Eloquent\Factories\Factory;
'first_name' => $faker->firstName,
'last_name' => $faker->lastName, class ContactFactory extends Factory
'email' => $faker->unique()->safeEmail, {
'phone' => $faker->tollFreePhoneNumber, /**
'address' => $faker->streetAddress, * The name of the factory's corresponding model.
'city' => $faker->city, *
'region' => $faker->state, * @var string
'country' => 'US', */
'postal_code' => $faker->postcode, protected $model = Contact::class;
];
}); /**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->unique()->safeEmail,
'phone' => $this->faker->tollFreePhoneNumber,
'address' => $this->faker->streetAddress,
'city' => $this->faker->city,
'region' => $this->faker->state,
'country' => 'US',
'postal_code' => $this->faker->postcode,
];
}
}

View File

@ -1,16 +1,35 @@
<?php <?php
use Faker\Generator as Faker; namespace Database\Factories;
$factory->define(App\Models\Organization::class, function (Faker $faker) { use App\Models\Organization;
return [ use Illuminate\Database\Eloquent\Factories\Factory;
'name' => $faker->company,
'email' => $faker->companyEmail, class OrganizationFactory extends Factory
'phone' => $faker->tollFreePhoneNumber, {
'address' => $faker->streetAddress, /**
'city' => $faker->city, * The name of the factory's corresponding model.
'region' => $faker->state, *
'country' => 'US', * @var string
'postal_code' => $faker->postcode, */
]; protected $model = Organization::class;
});
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->company,
'email' => $this->faker->companyEmail,
'phone' => $this->faker->tollFreePhoneNumber,
'address' => $this->faker->streetAddress,
'city' => $this->faker->city,
'region' => $this->faker->state,
'country' => 'US',
'postal_code' => $this->faker->postcode,
];
}
}

View File

@ -1,26 +1,49 @@
<?php <?php
use Faker\Generator as Faker; namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/* class UserFactory extends Factory
|-------------------------------------------------------------------------- {
| Model Factories /**
|-------------------------------------------------------------------------- * The name of the factory's corresponding model.
| *
| This directory should contain each of the model factory definitions for * @var string
| your application. Factories provide a convenient way to generate new */
| model instances for testing / seeding your application's database. protected $model = User::class;
|
*/
$factory->define(App\Models\User::class, function (Faker $faker) { /**
return [ * Define the model's default state.
'first_name' => $faker->firstName, *
'last_name' => $faker->lastName, * @return array
'email' => $faker->unique()->safeEmail, */
'password' => 'secret', public function definition()
'remember_token' => Str::random(10), {
'owner' => false, return [
]; 'first_name' => $this->faker->firstName,
}); 'last_name' => $this->faker->lastName,
'email' => $this->faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => 'secret',
'remember_token' => Str::random(10),
'owner' => false,
];
}
/**
* Indicate that the model's email address should be unverified.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function unverified()
{
return $this->state(function (array $attributes) {
return [
'email_verified_at' => null,
];
});
}
}

View File

@ -19,6 +19,7 @@ class CreateUsersTable extends Migration
$table->string('first_name', 25); $table->string('first_name', 25);
$table->string('last_name', 25); $table->string('last_name', 25);
$table->string('email', 50)->unique(); $table->string('email', 50)->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password')->nullable(); $table->string('password')->nullable();
$table->boolean('owner')->default(false); $table->boolean('owner')->default(false);
$table->string('photo_path', 100)->nullable(); $table->string('photo_path', 100)->nullable();

View File

@ -1,5 +1,7 @@
<?php <?php
namespace Database\Seeders;
use App\Models\Account; use App\Models\Account;
use App\Models\Contact; use App\Models\Contact;
use App\Models\Organization; use App\Models\Organization;
@ -8,11 +10,16 @@ use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder class DatabaseSeeder extends Seeder
{ {
/**
* Seed the application's database.
*
* @return void
*/
public function run() public function run()
{ {
$account = Account::create(['name' => 'Acme Corporation']); $account = Account::create(['name' => 'Acme Corporation']);
factory(User::class)->create([ User::factory()->create([
'account_id' => $account->id, 'account_id' => $account->id,
'first_name' => 'John', 'first_name' => 'John',
'last_name' => 'Doe', 'last_name' => 'Doe',
@ -20,12 +27,12 @@ class DatabaseSeeder extends Seeder
'owner' => true, 'owner' => true,
]); ]);
factory(User::class, 5)->create(['account_id' => $account->id]); User::factory(5)->create(['account_id' => $account->id]);
$organizations = factory(Organization::class, 100) $organizations = Organization::factory(100)
->create(['account_id' => $account->id]); ->create(['account_id' => $account->id]);
factory(Contact::class, 100) Contact::factory(100)
->create(['account_id' => $account->id]) ->create(['account_id' => $account->id])
->each(function ($contact) use ($organizations) { ->each(function ($contact) use ($organizations) {
$contact->update(['organization_id' => $organizations->random()->id]); $contact->update(['organization_id' => $organizations->random()->id]);

View File

@ -7,7 +7,8 @@
"watch-poll": "mix watch -- --watch-options-poll=1000", "watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot", "hot": "mix watch --hot",
"prod": "npm run production", "prod": "npm run production",
"production": "mix --production" "production": "mix --production",
"heroku-postbuild": "npm run prod"
}, },
"dependencies": { "dependencies": {
"@inertiajs/inertia": "^0.8.5", "@inertiajs/inertia": "^0.8.5",

View File

@ -1,23 +1,33 @@
<?php <?php
/** use Illuminate\Contracts\Http\Kernel;
* Laravel - A PHP Framework For Web Artisans use Illuminate\Http\Request;
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
require __DIR__.'/../storage/framework/maintenance.php';
}
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Register The Auto Loader | Register The Auto Loader
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Composer provides a convenient, automatically generated class loader for | Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it | this application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual | into the script here so we don't need to manually load our classes.
| loading any of our classes later on. It feels great to relax.
| |
*/ */
@ -25,36 +35,21 @@ require __DIR__.'/../vendor/autoload.php';
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Turn On The Lights | Run The Application
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| We need to illuminate PHP development, so let us turn on the lights. | Once we have the application, we can handle the incoming request using
| This bootstraps the framework and gets it ready for use, then it | the application's HTTP kernel. Then, we will send the response back
| will load up this application so that we can run it and send | to this client's browser, allowing them to enjoy our application.
| the responses back to the browser and delight our users.
| |
*/ */
$app = require_once __DIR__.'/../bootstrap/app.php'; $app = require_once __DIR__.'/../bootstrap/app.php';
/* $kernel = $app->make(Kernel::class);
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = tap($kernel->handle(
$request = Request::capture()
$response = $kernel->handle( ))->send();
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response); $kernel->terminate($request, $response);

View File

@ -1,6 +1,6 @@
<!-- <!--
Rewrites requires Microsoft URL Rewrite Module for IIS Rewrites requires Microsoft URL Rewrite Module for IIS
Download: https://www.microsoft.com/en-us/download/details.aspx?id=47337 Download: https://www.iis.net/downloads/microsoft/url-rewrite
Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules
--> -->
<configuration> <configuration>

View File

@ -3,7 +3,6 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\Account; use App\Models\Account;
use App\Models\Contact;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@ -16,77 +15,118 @@ class ContactsTest extends TestCase
{ {
parent::setUp(); parent::setUp();
$account = Account::create(['name' => 'Acme Corporation']); $this->user = User::factory()->create([
'account_id' => Account::create(['name' => 'Acme Corporation'])->id,
$this->user = factory(User::class)->create([
'account_id' => $account->id,
'first_name' => 'John', 'first_name' => 'John',
'last_name' => 'Doe', 'last_name' => 'Doe',
'email' => 'johndoe@example.com', 'email' => 'johndoe@example.com',
'owner' => true, 'owner' => true,
]); ]);
$organization = $this->user->account->organizations()->create(['name' => 'Example Organization Inc.']);
$this->user->account->contacts()->createMany([
[
'organization_id' => $organization->id,
'first_name' => 'Martin',
'last_name' => 'Abbott',
'email' => 'martin.abbott@example.com',
'phone' => '555-111-2222',
'address' => '330 Glenda Shore',
'city' => 'Murphyland',
'region' => 'Tennessee',
'country' => 'US',
'postal_code' => '57851',
], [
'organization_id' => $organization->id,
'first_name' => 'Lynn',
'last_name' => 'Kub',
'email' => 'lynn.kub@example.com',
'phone' => '555-333-4444',
'address' => '199 Connelly Turnpike',
'city' => 'Woodstock',
'region' => 'Colorado',
'country' => 'US',
'postal_code' => '11623',
],
]);
} }
public function test_can_view_contacts() public function test_can_view_contacts()
{ {
$this->user->account->contacts()->saveMany(
factory(Contact::class, 5)->make()
);
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/contacts') ->get('/contacts')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropCount('contacts.data', 5) ->component('Contacts/Index')
->assertPropValue('contacts.data', function ($contacts) { ->has('contacts.data', 2)
$this->assertEquals( ->has('contacts.data.0', fn ($assert) => $assert
['id', 'name', 'phone', 'city', ->where('id', 1)
'deleted_at', 'organization'], ->where('name', 'Martin Abbott')
array_keys($contacts[0]) ->where('phone', '555-111-2222')
); ->where('city', 'Murphyland')
}); ->where('deleted_at', null)
->has('organization', fn ($assert) => $assert
->where('name', 'Example Organization Inc.')
)
)
->has('contacts.data.1', fn ($assert) => $assert
->where('id', 2)
->where('name', 'Lynn Kub')
->where('phone', '555-333-4444')
->where('city', 'Woodstock')
->where('deleted_at', null)
->has('organization', fn ($assert) => $assert
->where('name', 'Example Organization Inc.')
)
)
);
} }
public function test_can_search_for_contacts() public function test_can_search_for_contacts()
{ {
$this->user->account->contacts()->saveMany(
factory(Contact::class, 5)->make()
)->first()->update([
'first_name' => 'Greg',
'last_name' => 'Andersson'
]);
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/contacts?search=Greg') ->get('/contacts?search=Martin')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropValue('filters.search', 'Greg') ->component('Contacts/Index')
->assertPropCount('contacts.data', 1) ->where('filters.search', 'Martin')
->assertPropValue('contacts.data', function ($contacts) { ->has('contacts.data', 1)
$this->assertEquals('Greg Andersson', $contacts[0]['name']); ->has('contacts.data.0', fn ($assert) => $assert
}); ->where('id', 1)
->where('name', 'Martin Abbott')
->where('phone', '555-111-2222')
->where('city', 'Murphyland')
->where('deleted_at', null)
->has('organization', fn ($assert) => $assert
->where('name', 'Example Organization Inc.')
)
)
);
} }
public function test_cannot_view_deleted_contacts() public function test_cannot_view_deleted_contacts()
{ {
$this->user->account->contacts()->saveMany( $this->user->account->contacts()->firstWhere('first_name', 'Martin')->delete();
factory(Contact::class, 5)->make()
)->first()->delete();
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/contacts') ->get('/contacts')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropCount('contacts.data', 4); ->component('Contacts/Index')
->has('contacts.data', 1)
->where('contacts.data.0.name', 'Lynn Kub')
);
} }
public function test_can_filter_to_view_deleted_contacts() public function test_can_filter_to_view_deleted_contacts()
{ {
$this->user->account->contacts()->saveMany( $this->user->account->contacts()->firstWhere('first_name', 'Martin')->delete();
factory(Contact::class, 5)->make()
)->first()->delete();
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/contacts?trashed=with') ->get('/contacts?trashed=with')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropValue('filters.trashed', 'with') ->component('Contacts/Index')
->assertPropCount('contacts.data', 5); ->has('contacts.data', 2)
->where('contacts.data.0.name', 'Martin Abbott')
->where('contacts.data.1.name', 'Lynn Kub')
);
} }
} }

View File

@ -4,7 +4,6 @@ namespace Tests\Feature;
use App\Models\Account; use App\Models\Account;
use App\Models\User; use App\Models\User;
use App\Models\Organization;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@ -16,73 +15,103 @@ class OrganizationsTest extends TestCase
{ {
parent::setUp(); parent::setUp();
$account = Account::create(['name' => 'Acme Corporation']); $this->user = User::factory()->create([
'account_id' => Account::create(['name' => 'Acme Corporation'])->id,
$this->user = factory(User::class)->create([
'account_id' => $account->id,
'first_name' => 'John', 'first_name' => 'John',
'last_name' => 'Doe', 'last_name' => 'Doe',
'email' => 'johndoe@example.com', 'email' => 'johndoe@example.com',
'owner' => true, 'owner' => true,
]); ]);
$this->user->account->organizations()->createMany([
[
'name' => 'Apple',
'email' => 'info@apple.com',
'phone' => '647-943-4400',
'address' => '1600-120 Bremner Blvd',
'city' => 'Toronto',
'region' => 'ON',
'country' => 'CA',
'postal_code' => 'M5J 0A8',
], [
'name' => 'Microsoft',
'email' => 'info@microsoft.com',
'phone' => '877-568-2495',
'address' => 'One Microsoft Way',
'city' => 'Redmond',
'region' => 'WA',
'country' => 'US',
'postal_code' => '98052',
],
]);
} }
public function test_can_view_organizations() public function test_can_view_organizations()
{ {
$this->user->account->organizations()->saveMany(
factory(Organization::class, 5)->make()
);
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/organizations') ->get('/organizations')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropCount('organizations.data', 5) ->component('Organizations/Index')
->assertPropValue('organizations.data', function ($organizations) { ->has('organizations.data', 2)
$this->assertEquals( ->has('organizations.data.0', fn ($assert) => $assert
['id', 'name', 'phone', 'city', 'deleted_at'], ->where('id', 1)
array_keys($organizations[0]) ->where('name', 'Apple')
); ->where('phone', '647-943-4400')
}); ->where('city', 'Toronto')
->where('deleted_at', null)
)
->has('organizations.data.1', fn ($assert) => $assert
->where('id', 2)
->where('name', 'Microsoft')
->where('phone', '877-568-2495')
->where('city', 'Redmond')
->where('deleted_at', null)
)
);
} }
public function test_can_search_for_organizations() public function test_can_search_for_organizations()
{ {
$this->user->account->organizations()->saveMany(
factory(Organization::class, 5)->make()
)->first()->update(['name' => 'Some Big Fancy Company Name']);
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/organizations?search=Some Big Fancy Company Name') ->get('/organizations?search=Apple')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropValue('filters.search', 'Some Big Fancy Company Name') ->component('Organizations/Index')
->assertPropCount('organizations.data', 1) ->where('filters.search', 'Apple')
->assertPropValue('organizations.data', function ($organizations) { ->has('organizations.data', 1)
$this->assertEquals('Some Big Fancy Company Name', $organizations[0]['name']); ->has('organizations.data.0', fn ($assert) => $assert
}); ->where('id', 1)
->where('name', 'Apple')
->where('phone', '647-943-4400')
->where('city', 'Toronto')
->where('deleted_at', null)
)
);
} }
public function test_cannot_view_deleted_organizations() public function test_cannot_view_deleted_organizations()
{ {
$this->user->account->organizations()->saveMany( $this->user->account->organizations()->firstWhere('name', 'Microsoft')->delete();
factory(Organization::class, 5)->make()
)->first()->delete();
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/organizations') ->get('/organizations')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropCount('organizations.data', 4); ->component('Organizations/Index')
->has('organizations.data', 1)
->where('organizations.data.0.name', 'Apple')
);
} }
public function test_can_filter_to_view_deleted_organizations() public function test_can_filter_to_view_deleted_organizations()
{ {
$this->user->account->organizations()->saveMany( $this->user->account->organizations()->firstWhere('name', 'Microsoft')->delete();
factory(Organization::class, 5)->make()
)->first()->delete();
$this->actingAs($this->user) $this->actingAs($this->user)
->get('/organizations?trashed=with') ->get('/organizations?trashed=with')
->assertStatus(200) ->assertInertia(fn ($assert) => $assert
->assertPropValue('filters.trashed', 'with') ->component('Organizations/Index')
->assertPropCount('organizations.data', 5); ->has('organizations.data', 2)
->where('organizations.data.0.name', 'Apple')
->where('organizations.data.1.name', 'Microsoft')
);
} }
} }

View File

@ -3,52 +3,8 @@
namespace Tests; namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Arr;
use Illuminate\Testing\TestResponse;
use PHPUnit\Framework\Assert;
abstract class TestCase extends BaseTestCase abstract class TestCase extends BaseTestCase
{ {
use CreatesApplication; use CreatesApplication;
protected function setUp(): void
{
parent::setUp();
TestResponse::macro('props', function ($key = null) {
$props = json_decode(json_encode($this->original->getData()['page']['props']), JSON_OBJECT_AS_ARRAY);
if ($key) {
return Arr::get($props, $key);
}
return $props;
});
TestResponse::macro('assertHasProp', function ($key) {
Assert::assertTrue(Arr::has($this->props(), $key));
return $this;
});
TestResponse::macro('assertPropValue', function ($key, $value) {
$this->assertHasProp($key);
if (is_callable($value)) {
$value($this->props($key));
} else {
Assert::assertEquals($this->props($key), $value);
}
return $this;
});
TestResponse::macro('assertPropCount', function ($key, $count) {
$this->assertHasProp($key);
Assert::assertCount($count, $this->props($key));
return $this;
});
}
} }

View File

@ -0,0 +1,18 @@
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function test_example()
{
$this->assertTrue(true);
}
}