From b8d0434bd27baef0115989bf37bdeaf2d94ad162 Mon Sep 17 00:00:00 2001 From: Jack Ellis Date: Sun, 18 Aug 2024 20:26:25 +0100 Subject: [PATCH] Feature/bootstrap and font awesome (#6) * Updates to frontendery * wip M _docker/plugins/default-login.php M app/Models/Person.php M app/Models/Team.php M database/factories/TeamFactory.php M resources/views/layouts/app.blade.php M resources/views/team/index.blade.php M routes/auth.php M tests/Feature/Auth/PasswordUpdateTest.php * additional frontendery M app/View/Components/Input.php M resources/views/components/input.blade.php M resources/views/layouts/app.blade.php M resources/views/team/index.blade.php * Remove bootstrap JS and tailwind * update nav M resources/views/layouts/navigation.blade.php * Add option to just log in as a user locally without a password * Add validationexception to throwing * Update user id in sessions table * class updates M resources/views/components/input.blade.php M resources/views/layouts/navigation.blade.php * remove old nav M resources/views/layouts/navigation.blade.php * wip M app/Http/Controllers/CompetitionController.php M app/Http/Controllers/GameController.php M app/Http/Controllers/PersonController.php M app/Http/Controllers/PositionController.php M app/Http/Controllers/SportController.php M app/Http/Controllers/TeamController.php M app/Models/Game.php M app/Models/Position.php M app/Providers/AppServiceProvider.php M database/factories/CompetitionFactory.php M database/factories/GameFactory.php M database/factories/PersonFactory.php M database/factories/PositionFactory.php M database/factories/SportFactory.php M database/factories/TeamFactory.php M resources/views/auth/login.blade.php M resources/views/competition/create.blade.php M resources/views/competition/edit.blade.php M resources/views/competition/index.blade.php M resources/views/game/create.blade.php M resources/views/game/edit.blade.php M resources/views/game/index.blade.php M resources/views/person/create.blade.php M resources/views/person/edit.blade.php M resources/views/position/create.blade.php M resources/views/position/edit.blade.php M resources/views/position/index.blade.php M resources/views/sport/index.blade.php M stubs/controller.model.stub * testing and some binaries starting M app/Http/Controllers/Auth/AuthenticatedSessionController.php M app/Http/Controllers/LocalLoginAsUserController.php M app/Models/Game.php M app/Models/Position.php M app/Providers/AppServiceProvider.php M app/View/Components/Input.php AM bin/util/exec AM bin/util/install M composer.json M database/factories/PositionFactory.php M database/factories/SportFactory.php M database/migrations/0001_01_01_000000_create_users_table.php M routes/auth.php ?? .github/ ?? bin/util/start * make them executable M bin/util/exec M bin/util/install M bin/util/start * remove - M bin/util/install * missed a word M .github/workflows/testing.yaml * amends for CI M app/Console/Commands/MakeEntity.php M app/Http/Controllers/Auth/ConfirmablePasswordController.php M app/Http/Controllers/Auth/EmailVerificationNotificationController.php M app/Http/Controllers/Auth/EmailVerificationPromptController.php M app/Http/Controllers/Auth/PasswordController.php M app/Http/Controllers/Auth/VerifyEmailController.php M app/Http/Controllers/LocalLoginAsUserController.php M app/Http/Controllers/ProfileController.php M app/Http/Requests/Auth/LoginRequest.php M app/Http/Requests/ProfileUpdateRequest.php M app/Models/Competition.php M app/Models/Game.php M app/Models/Person.php M app/Models/Position.php M app/Models/Sport.php M app/Models/Team.php M app/Models/User.php M app/View/Components/Input.php M composer.json M database/factories/PositionFactory.php * amend model stub M stubs/model.stub --- .github/dependabot.yaml | 20 +++ .github/workflows/automerge-dependabot.yaml | 37 +++++ .github/workflows/testing.yaml | 44 +++++ _docker/plugins/default-login.php | 2 +- app/Console/Commands/MakeEntity.php | 3 + .../Auth/AuthenticatedSessionController.php | 3 + .../Auth/ConfirmablePasswordController.php | 6 + ...mailVerificationNotificationController.php | 6 + .../EmailVerificationPromptController.php | 6 + .../Controllers/Auth/PasswordController.php | 6 + .../Auth/VerifyEmailController.php | 20 ++- .../Controllers/CompetitionController.php | 2 +- app/Http/Controllers/GameController.php | 2 +- .../LocalLoginAsUserController.php | 29 ++++ app/Http/Controllers/PersonController.php | 2 +- app/Http/Controllers/PositionController.php | 2 +- app/Http/Controllers/ProfileController.php | 8 + app/Http/Controllers/SportController.php | 2 +- app/Http/Controllers/TeamController.php | 2 +- app/Http/Requests/Auth/LoginRequest.php | 3 +- app/Http/Requests/ProfileUpdateRequest.php | 11 +- app/Models/Competition.php | 2 + app/Models/Game.php | 33 +++- app/Models/Person.php | 19 ++- app/Models/Position.php | 15 +- app/Models/Sport.php | 2 + app/Models/Team.php | 4 +- app/Models/User.php | 2 + app/Providers/AppServiceProvider.php | 16 +- app/View/Components/Input.php | 16 +- bin/util/exec | 7 + bin/util/install | 8 + bin/util/start | 3 + composer.json | 9 +- database/factories/CompetitionFactory.php | 6 + database/factories/GameFactory.php | 9 ++ database/factories/PersonFactory.php | 5 + database/factories/PositionFactory.php | 20 +++ database/factories/SportFactory.php | 2 + database/factories/TeamFactory.php | 8 + .../0001_01_01_000000_create_users_table.php | 3 +- package-lock.json | 66 +++++++- package.json | 5 + resources/css/app.css | 3 - resources/js/app.js | 4 + resources/scss/app.scss | 15 ++ resources/views/auth/login.blade.php | 15 +- resources/views/competition/create.blade.php | 2 +- resources/views/competition/edit.blade.php | 2 +- resources/views/competition/index.blade.php | 9 ++ resources/views/components/input.blade.php | 14 +- resources/views/game/create.blade.php | 6 +- resources/views/game/edit.blade.php | 6 +- resources/views/game/index.blade.php | 14 ++ resources/views/layouts/app.blade.php | 6 +- resources/views/layouts/guest.blade.php | 2 +- resources/views/layouts/navigation.blade.php | 150 +++++++----------- resources/views/person/create.blade.php | 2 +- resources/views/person/edit.blade.php | 2 +- resources/views/position/create.blade.php | 2 +- resources/views/position/edit.blade.php | 2 +- resources/views/position/index.blade.php | 19 ++- resources/views/sport/index.blade.php | 9 ++ resources/views/team/index.blade.php | 28 ++++ routes/auth.php | 14 +- stubs/controller.model.stub | 2 +- stubs/model.stub | 2 + tests/Feature/Auth/PasswordUpdateTest.php | 3 +- vite.config.js | 7 +- 69 files changed, 657 insertions(+), 159 deletions(-) create mode 100644 .github/dependabot.yaml create mode 100644 .github/workflows/automerge-dependabot.yaml create mode 100644 .github/workflows/testing.yaml create mode 100644 app/Http/Controllers/LocalLoginAsUserController.php create mode 100755 bin/util/exec create mode 100755 bin/util/install create mode 100755 bin/util/start delete mode 100644 resources/css/app.css create mode 100644 resources/scss/app.scss diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..d6cb879 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "00:00" + timezone: "Europe/London" + open-pull-requests-limit: 10 + commit-message: + prefix: "[DEPS][NPM]" + - package-ecosystem: composer + directory: "/" + schedule: + interval: daily + time: "00:00" + timezone: "Europe/London" + open-pull-requests-limit: 10 + commit-message: + prefix: "[DEPS][COMPOSER]" diff --git a/.github/workflows/automerge-dependabot.yaml b/.github/workflows/automerge-dependabot.yaml new file mode 100644 index 0000000..48c2d5e --- /dev/null +++ b/.github/workflows/automerge-dependabot.yaml @@ -0,0 +1,37 @@ +name: Auto-merge Dependabot PRs +on: + pull_request: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + name: Auto-merge dependabot where appropriate + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1.1.1 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Leave a comment with the metadata extracted + run: gh pr comment "$PR_URL" --body "Type is ${{steps.metadata.outputs.update-type}}" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Enable auto-merge for Dependabot PRs + if: | + endsWith(steps.metadata.outputs.update-type, 'semver-patch') || + endsWith(steps.metadata.outputs.update-type, 'semver-minor') + run: | + gh pr comment "$PR_URL" --body "Merging" && \ + gh pr review --approve "$PR_URL" && \ + gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml new file mode 100644 index 0000000..b6acfa1 --- /dev/null +++ b/.github/workflows/testing.yaml @@ -0,0 +1,44 @@ +name: Testing + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + testing: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Cache Composer + id: cache-composer + uses: actions/cache@v3 + with: + path: vendor + key: ${{ hashFiles('composer.lock') }} + - name: Cache NPM + id: cache-npm + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ hashFiles('package-lock.json') }} + - name: Install + run: ./bin/util/install + - name: Start + run: ./bin/util/start +# - name: Deptrac +# run: ./_docker/bin/app/composer deptrac +# if: always() + - name: ECS + run: ./bin/util/exec app composer cs:check + if: always() + - name: Stan + run: ./bin/util/exec app composer stan + if: always() +# - name: PHPUnit +# run: ./_docker/bin/app/phpunit-with-coverage +# if: always() diff --git a/_docker/plugins/default-login.php b/_docker/plugins/default-login.php index 36d0c0b..bb3afb0 100644 --- a/_docker/plugins/default-login.php +++ b/_docker/plugins/default-login.php @@ -37,7 +37,7 @@ public function loginForm() public function loginFormField(string $name, string $envValue = '', string $value = ''): string { $inputValue = $value; - if (! empty($envValue) && isset($_ENV[$envValue])) { + if (!empty($envValue) && isset($_ENV[$envValue])) { $inputValue = $_ENV[$envValue]; } diff --git a/app/Console/Commands/MakeEntity.php b/app/Console/Commands/MakeEntity.php index f897607..65ed549 100644 --- a/app/Console/Commands/MakeEntity.php +++ b/app/Console/Commands/MakeEntity.php @@ -65,6 +65,9 @@ public function handle(): int return self::SUCCESS; } + /** + * @return array + */ private function getCrudOperations(): array { return [ diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php index 613bcd9..e06ed36 100644 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -7,6 +7,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Validation\ValidationException; use Illuminate\View\View; class AuthenticatedSessionController extends Controller @@ -21,6 +22,8 @@ public function create(): View /** * Handle an incoming authentication request. + * + * @throws ValidationException */ public function store(LoginRequest $request): RedirectResponse { diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php index fb910d2..21000b3 100644 --- a/app/Http/Controllers/Auth/ConfirmablePasswordController.php +++ b/app/Http/Controllers/Auth/ConfirmablePasswordController.php @@ -24,6 +24,12 @@ public function show(): View */ public function store(Request $request): RedirectResponse { + if (!$request->user()) { + $request->session()->flash('status', __('auth.failed')); + + return redirect()->route('login'); + } + if (!Auth::guard('web')->validate([ 'email' => $request->user()->email, 'password' => $request->password, diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php index f64fa9b..8a1dba0 100644 --- a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php +++ b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php @@ -13,6 +13,12 @@ class EmailVerificationNotificationController extends Controller */ public function store(Request $request): RedirectResponse { + if (!$request->user()) { + $request->session()->flash('status', __('auth.failed')); + + return redirect()->route('login'); + } + if ($request->user()->hasVerifiedEmail()) { return redirect()->intended(route('dashboard', absolute: false)); } diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php index ee3cb6f..63c2cf9 100644 --- a/app/Http/Controllers/Auth/EmailVerificationPromptController.php +++ b/app/Http/Controllers/Auth/EmailVerificationPromptController.php @@ -14,6 +14,12 @@ class EmailVerificationPromptController extends Controller */ public function __invoke(Request $request): RedirectResponse|View { + if (!$request->user()) { + $request->session()->flash('status', __('auth.failed')); + + return redirect()->route('login'); + } + return $request->user()->hasVerifiedEmail() ? redirect()->intended(route('dashboard', absolute: false)) : view('auth.verify-email'); diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php index 6916409..448a8ad 100644 --- a/app/Http/Controllers/Auth/PasswordController.php +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -15,6 +15,12 @@ class PasswordController extends Controller */ public function update(Request $request): RedirectResponse { + if (!$request->user()) { + $request->session()->flash('error', __('auth.failed')); + + return redirect()->route('login'); + } + $validated = $request->validateWithBag('updatePassword', [ 'current_password' => ['required', 'current_password'], 'password' => ['required', Password::defaults(), 'confirmed'], diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php index 784765e..df397aa 100644 --- a/app/Http/Controllers/Auth/VerifyEmailController.php +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use Illuminate\Auth\Events\Verified; +use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\EmailVerificationRequest; use Illuminate\Http\RedirectResponse; @@ -14,12 +15,25 @@ class VerifyEmailController extends Controller */ public function __invoke(EmailVerificationRequest $request): RedirectResponse { - if ($request->user()->hasVerifiedEmail()) { + $user = $request->user(); + if (is_null($user)) { + $request->session()->flash('error', __('auth.failed')); + + return redirect()->route('login'); + } + + if (!$user instanceof MustVerifyEmail) { + $request->session()->flash('error', __('auth.failed')); + + return redirect()->route('dashboard'); + } + + if ($user->hasVerifiedEmail()) { return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); } - if ($request->user()->markEmailAsVerified()) { - event(new Verified($request->user())); + if ($user->markEmailAsVerified()) { + event(new Verified($user)); } return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); diff --git a/app/Http/Controllers/CompetitionController.php b/app/Http/Controllers/CompetitionController.php index b9be409..e234b1c 100644 --- a/app/Http/Controllers/CompetitionController.php +++ b/app/Http/Controllers/CompetitionController.php @@ -20,7 +20,7 @@ public function index(): Application|Factory|View return view( 'competition.index', [ - 'competitions' => Competition::all(), + 'competitions' => Competition::get(), ] ); } diff --git a/app/Http/Controllers/GameController.php b/app/Http/Controllers/GameController.php index e929eef..826e46b 100644 --- a/app/Http/Controllers/GameController.php +++ b/app/Http/Controllers/GameController.php @@ -20,7 +20,7 @@ public function index(): Application|Factory|View return view( 'game.index', [ - 'games' => Game::all(), + 'games' => Game::get(), ] ); } diff --git a/app/Http/Controllers/LocalLoginAsUserController.php b/app/Http/Controllers/LocalLoginAsUserController.php new file mode 100644 index 0000000..198c330 --- /dev/null +++ b/app/Http/Controllers/LocalLoginAsUserController.php @@ -0,0 +1,29 @@ +input('user_id'); + $user = User::find($userId); + if ($user instanceof User) { + Auth::login($user); + + return to_route('dashboard'); + } + + $request->session()->flash('error', 'User not found'); + + return to_route('login'); + } +} diff --git a/app/Http/Controllers/PersonController.php b/app/Http/Controllers/PersonController.php index a664122..bc38c69 100644 --- a/app/Http/Controllers/PersonController.php +++ b/app/Http/Controllers/PersonController.php @@ -20,7 +20,7 @@ public function index(): Application|Factory|View return view( 'person.index', [ - 'people' => Person::all(), + 'people' => Person::get(), ] ); } diff --git a/app/Http/Controllers/PositionController.php b/app/Http/Controllers/PositionController.php index cc3f1bc..aa36c26 100644 --- a/app/Http/Controllers/PositionController.php +++ b/app/Http/Controllers/PositionController.php @@ -20,7 +20,7 @@ public function index(): Application|Factory|View return view( 'position.index', [ - 'positions' => Position::all(), + 'positions' => Position::get(), ] ); } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index a48eb8d..6b7909a 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -26,6 +26,10 @@ public function edit(Request $request): View */ public function update(ProfileUpdateRequest $request): RedirectResponse { + if (!$request->user()) { + return redirect()->route('login'); + } + $request->user()->fill($request->validated()); if ($request->user()->isDirty('email')) { @@ -42,6 +46,10 @@ public function update(ProfileUpdateRequest $request): RedirectResponse */ public function destroy(Request $request): RedirectResponse { + if (!$request->user()) { + return redirect()->route('login'); + } + $request->validateWithBag('userDeletion', [ 'password' => ['required', 'current_password'], ]); diff --git a/app/Http/Controllers/SportController.php b/app/Http/Controllers/SportController.php index 6032b47..820ddde 100644 --- a/app/Http/Controllers/SportController.php +++ b/app/Http/Controllers/SportController.php @@ -20,7 +20,7 @@ public function index(): Application|Factory|View return view( 'sport.index', [ - 'sports' => Sport::all(), + 'sports' => Sport::get(), ] ); } diff --git a/app/Http/Controllers/TeamController.php b/app/Http/Controllers/TeamController.php index 08c9403..fffc1fa 100644 --- a/app/Http/Controllers/TeamController.php +++ b/app/Http/Controllers/TeamController.php @@ -20,7 +20,7 @@ public function index(): Application|Factory|View return view( 'team.index', [ - 'teams' => Team::all(), + 'teams' => Team::get(), ] ); } diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php index 5ee9ddc..29ec5f4 100644 --- a/app/Http/Requests/Auth/LoginRequest.php +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\Auth; use Illuminate\Auth\Events\Lockout; +use Illuminate\Contracts\Validation\Rule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\RateLimiter; @@ -22,7 +23,7 @@ public function authorize(): bool /** * Get the validation rules that apply to the request. * - * @return array + * @return array|Rule|string> */ public function rules(): array { diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php index 29f1a9c..6fca366 100644 --- a/app/Http/Requests/ProfileUpdateRequest.php +++ b/app/Http/Requests/ProfileUpdateRequest.php @@ -11,13 +11,20 @@ class ProfileUpdateRequest extends FormRequest /** * Get the validation rules that apply to the request. * - * @return array + * @return array|Rule|string> */ public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], - 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], + 'email' => [ + 'required', + 'string', + 'lowercase', + 'email', + 'max:255', + Rule::unique(User::class)->ignore($this->user()?->id), + ], ]; } } diff --git a/app/Models/Competition.php b/app/Models/Competition.php index 3d9e1d7..727605c 100644 --- a/app/Models/Competition.php +++ b/app/Models/Competition.php @@ -2,6 +2,7 @@ namespace App\Models; +use Database\Factories\CompetitionFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -9,6 +10,7 @@ class Competition extends Model { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use SoftDeletes; diff --git a/app/Models/Game.php b/app/Models/Game.php index 5c2226c..9fa2212 100644 --- a/app/Models/Game.php +++ b/app/Models/Game.php @@ -2,13 +2,16 @@ namespace App\Models; +use Database\Factories\GameFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; class Game extends Model { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use SoftDeletes; @@ -25,7 +28,35 @@ class Game extends Model * * @var array */ - protected $with = []; + protected $with = [ + 'team1', + 'team2', + 'competition', + ]; + + /** + * @return BelongsTo + */ + public function team1(): BelongsTo + { + return $this->belongsTo(Team::class, 'team_1_id'); + } + + /** + * @return BelongsTo + */ + public function team2(): BelongsTo + { + return $this->belongsTo(Team::class, 'team_2_id'); + } + + /** + * @return BelongsTo + */ + public function competition(): BelongsTo + { + return $this->belongsTo(Competition::class); + } /** * Get the attributes that should be cast. diff --git a/app/Models/Person.php b/app/Models/Person.php index b221e30..bd96d2a 100644 --- a/app/Models/Person.php +++ b/app/Models/Person.php @@ -2,6 +2,7 @@ namespace App\Models; +use Database\Factories\PersonFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -10,6 +11,7 @@ class Person extends Model { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use SoftDeletes; @@ -23,7 +25,7 @@ class Person extends Model 'name', 'dob', 'bio', - 'user_id' + 'user_id', ]; /** @@ -32,9 +34,17 @@ class Person extends Model * @var array */ protected $with = [ - 'user' + 'user', ]; + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + /** * Get the attributes that should be cast. * @@ -44,9 +54,4 @@ protected function casts(): array { return []; } - - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } } diff --git a/app/Models/Position.php b/app/Models/Position.php index bfac0af..c0e0661 100644 --- a/app/Models/Position.php +++ b/app/Models/Position.php @@ -2,13 +2,16 @@ namespace App\Models; +use Database\Factories\PositionFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; class Position extends Model { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use SoftDeletes; @@ -25,7 +28,17 @@ class Position extends Model * * @var array */ - protected $with = []; + protected $with = [ + 'sport', + ]; + + /** + * @return BelongsTo + */ + public function sport(): BelongsTo + { + return $this->belongsTo(Sport::class); + } /** * Get the attributes that should be cast. diff --git a/app/Models/Sport.php b/app/Models/Sport.php index f6039f4..1458992 100644 --- a/app/Models/Sport.php +++ b/app/Models/Sport.php @@ -2,6 +2,7 @@ namespace App\Models; +use Database\Factories\SportFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -9,6 +10,7 @@ class Sport extends Model { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use SoftDeletes; diff --git a/app/Models/Team.php b/app/Models/Team.php index 0531b97..58cbc23 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -2,6 +2,7 @@ namespace App\Models; +use Database\Factories\TeamFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -9,6 +10,7 @@ class Team extends Model { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use SoftDeletes; @@ -35,7 +37,7 @@ class Team extends Model protected function casts(): array { return [ - 'colours' => 'array' + 'colours' => 'array', ]; } } diff --git a/app/Models/User.php b/app/Models/User.php index c477ff3..7c05ae5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,6 +2,7 @@ namespace App\Models; +use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Concerns\HasVersion7Uuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; @@ -10,6 +11,7 @@ class User extends Authenticatable { + /** @use HasFactory */ use HasFactory; use HasVersion7Uuids; use Notifiable; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8060b78..dede4ed 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,11 @@ namespace App\Providers; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Events\QueryExecuted; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -14,5 +19,14 @@ public function register(): void {} /** * Bootstrap any application services. */ - public function boot(): void {} + public function boot(): void + { + Model::shouldBeStrict(true); + + if (App::hasDebugModeEnabled()) { + DB::listen(function (QueryExecuted $query) { + Log::info($query->toRawSql()); + }); + } + } } diff --git a/app/View/Components/Input.php b/app/View/Components/Input.php index 02073e4..3cb41bf 100644 --- a/app/View/Components/Input.php +++ b/app/View/Components/Input.php @@ -12,9 +12,10 @@ class Input extends Component public readonly \Closure $textFn; public readonly string $label; public readonly UuidV7 $id; + public readonly string $inputClass; /** - * Create a new component instance. + * @param iterable $options */ public function __construct( public readonly string $name, @@ -28,8 +29,19 @@ public function __construct( ) { $this->valueFn = $valueFn ?? fn ($input) => $input->id; $this->textFn = $textFn ?? fn ($input) => $input->name; - $this->label = $label ?? ucwords(preg_replace('/[^A-Za-z0-9]/', ' ', $name)); + if (!is_null($label)) { + $this->label = $label; + } else { + $spacedName = preg_replace('/[^A-Za-z0-9]/', ' ', $name); + $this->label = ucwords($spacedName ?? 'Error getting name'); + } $this->id = new UuidV7(); + + $this->inputClass = match ($type) { + 'color' => 'form-control-color', + 'select' => 'form-select', + default => 'form-control', + }; } /** diff --git a/bin/util/exec b/bin/util/exec new file mode 100755 index 0000000..e017fc9 --- /dev/null +++ b/bin/util/exec @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then + docker compose exec "$@" +else + docker compose exec -T "$@" +fi diff --git a/bin/util/install b/bin/util/install new file mode 100755 index 0000000..5ad666f --- /dev/null +++ b/bin/util/install @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +cp .env.example .env \ + && docker compose up -d --build \ + && ./bin/util/exec app composer install \ + && ./bin/util/exec node npm install \ + && ./bin/util/exec node npm run build \ + && ./bin/util/exec app php artisan migrate:fresh diff --git a/bin/util/start b/bin/util/start new file mode 100755 index 0000000..68702f0 --- /dev/null +++ b/bin/util/start @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +docker compose up -d diff --git a/composer.json b/composer.json index c1a2b3f..d43b01d 100644 --- a/composer.json +++ b/composer.json @@ -49,10 +49,11 @@ "@php artisan migrate --graceful --ansi" ], "stan": "./vendor/bin/phpstan analyse --memory-limit=2G", - "fix": "./vendor/bin/php-cs-fixer fix", - "check": [ - "@stan", - "@fix" + "cs:fix": "./vendor/bin/php-cs-fixer fix", + "cs:check": "./vendor/bin/php-cs-fixer check", + "preflight": [ + "@cs:fix", + "@stan" ] }, "extra": { diff --git a/database/factories/CompetitionFactory.php b/database/factories/CompetitionFactory.php index f880a65..296ced2 100644 --- a/database/factories/CompetitionFactory.php +++ b/database/factories/CompetitionFactory.php @@ -2,6 +2,8 @@ namespace Database\Factories; +use App\Models\Competition; +use App\Models\Sport; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -17,6 +19,10 @@ class CompetitionFactory extends Factory public function definition(): array { return [ + 'parent_id' => $this->faker->boolean(33) ? Competition::factory() : null, + 'name' => $this->faker->name(), + 'description' => $this->faker->text(), + 'sport_id' => Sport::count() < 10 ? Sport::factory() : Sport::get()->random(), ]; } } diff --git a/database/factories/GameFactory.php b/database/factories/GameFactory.php index 8851461..f7bbde6 100644 --- a/database/factories/GameFactory.php +++ b/database/factories/GameFactory.php @@ -2,6 +2,8 @@ namespace Database\Factories; +use App\Models\Competition; +use App\Models\Team; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -17,6 +19,13 @@ class GameFactory extends Factory public function definition(): array { return [ + 'name' => $this->faker->name(), + 'start' => $this->faker->dateTime(), + 'description' => $this->faker->text(), + 'summary' => $this->faker->text(), + 'competition_id' => Competition::count() < 10 ? Competition::factory() : Competition::get()->random(), + 'team_1_id' => Team::count() < 10 ? Team::factory() : Team::get()->random(), + 'team_2_id' => Team::count() < 10 ? Team::factory() : Team::get()->random(), ]; } } diff --git a/database/factories/PersonFactory.php b/database/factories/PersonFactory.php index 780b332..a6b67c6 100644 --- a/database/factories/PersonFactory.php +++ b/database/factories/PersonFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -17,6 +18,10 @@ class PersonFactory extends Factory public function definition(): array { return [ + 'name' => $this->faker->name(), + 'bio' => $this->faker->text(), + 'dob' => $this->faker->date(), + 'user_id' => $this->faker->boolean() ? User::factory() : null, ]; } } diff --git a/database/factories/PositionFactory.php b/database/factories/PositionFactory.php index b843e8b..9fe762b 100644 --- a/database/factories/PositionFactory.php +++ b/database/factories/PositionFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Models\Sport; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -16,7 +17,26 @@ class PositionFactory extends Factory */ public function definition(): array { + /** @var int<0,2> $leftRightOrMiddle */ + $leftRightOrMiddle = $this->faker->numberBetween(0, 2); + $prefix = match ($leftRightOrMiddle) { + 0 => 'Left', + 1 => $this->faker->boolean() ? 'Full' : 'Middle', + 2 => 'Right' + }; + return [ + 'name' => $prefix.' '.$this->faker->word(), + 'description' => $this->faker->text(), + 'preview_x' => $this->faker->numberBetween(0, 100), + 'preview_y' => match ($leftRightOrMiddle) { + 0 => $this->faker->numberBetween(0, 33), + 1 => $this->faker->numberBetween(34, 66), + 2 => $this->faker->numberBetween(67, 100), + }, + 'sort_order' => $this->faker->numberBetween(0, 100), + 'default_number' => $this->faker->numberBetween(0, 100), + 'sport_id' => Sport::count() < 10 ? Sport::factory() : Sport::get()->random(), ]; } } diff --git a/database/factories/SportFactory.php b/database/factories/SportFactory.php index 0d5681d..4f28b25 100644 --- a/database/factories/SportFactory.php +++ b/database/factories/SportFactory.php @@ -17,6 +17,8 @@ class SportFactory extends Factory public function definition(): array { return [ + 'name' => $this->faker->word().'ball', + 'description' => $this->faker->paragraph(), ]; } } diff --git a/database/factories/TeamFactory.php b/database/factories/TeamFactory.php index 83db692..515a00d 100644 --- a/database/factories/TeamFactory.php +++ b/database/factories/TeamFactory.php @@ -16,7 +16,15 @@ class TeamFactory extends Factory */ public function definition(): array { + $colours = []; + for ($n = 0; $n < $this->faker->numberBetween(1, 10); ++$n) { + $colours[] = $this->faker->hexColor(); + } + return [ + 'name' => $this->faker->name(), + 'description' => $this->faker->text(), + 'colours' => $colours, ]; } } diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 6e92488..65ee46c 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -1,5 +1,6 @@ string('id')->primary(); - $table->foreignId('user_id')->nullable()->index(); + $table->foreignIdFor(User::class)->nullable()->index(); $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->longText('payload'); diff --git a/package-lock.json b/package-lock.json index 59fa530..3917586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,13 @@ { - "name": "clubhouse-laravel", + "name": "app", "lockfileVersion": 3, "requires": true, "packages": { "": { + "dependencies": { + "@fortawesome/fontawesome-free": "^6.6.0", + "bootstrap": "^5.3.3" + }, "devDependencies": { "@tailwindcss/forms": "^0.5.2", "alpinejs": "^3.4.2", @@ -11,6 +15,7 @@ "axios": "^1.6.4", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.31", + "sass": "^1.77.8", "tailwindcss": "^3.1.0", "vite": "^5.0" } @@ -419,6 +424,14 @@ "node": ">=12" } }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz", + "integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==", + "engines": { + "node": ">=6" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -539,6 +552,16 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", @@ -941,6 +964,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -1423,6 +1464,12 @@ "node": ">= 0.4" } }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2117,6 +2164,23 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sass": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index e158669..85a9058 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,12 @@ "axios": "^1.6.4", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.31", + "sass": "^1.77.8", "tailwindcss": "^3.1.0", "vite": "^5.0" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.6.0", + "bootstrap": "^5.3.3" } } diff --git a/resources/css/app.css b/resources/css/app.css deleted file mode 100644 index b5c61c9..0000000 --- a/resources/css/app.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/resources/js/app.js b/resources/js/app.js index a8093be..fb1cf24 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,5 +1,9 @@ import './bootstrap'; +import '../scss/app.scss'; + +// import * as bootstrap from 'bootstrap'; + import Alpine from 'alpinejs'; window.Alpine = Alpine; diff --git a/resources/scss/app.scss b/resources/scss/app.scss new file mode 100644 index 0000000..4464ee1 --- /dev/null +++ b/resources/scss/app.scss @@ -0,0 +1,15 @@ +//@tailwind base; +//@tailwind components; +//@tailwind utilities; + +$fa-font-path: "@fortawesome/fontawesome-free/webfonts"; + +@import "@fortawesome/fontawesome-free/scss/fontawesome"; +@import "@fortawesome/fontawesome-free/scss/brands"; +@import "@fortawesome/fontawesome-free/scss/regular"; +@import "@fortawesome/fontawesome-free/scss/solid"; + +$primary: #a54499; +$secondary: #ffc425; + +@import "bootstrap/scss/bootstrap"; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 78b684f..1e80f7c 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,7 +1,18 @@ - + + @env('local') +
+ @csrf +
(DEV) Log In As User
+
+ + +
+
+ @endenv +
@csrf @@ -44,4 +55,4 @@
-
+ diff --git a/resources/views/competition/create.blade.php b/resources/views/competition/create.blade.php index bfb6b04..6238359 100644 --- a/resources/views/competition/create.blade.php +++ b/resources/views/competition/create.blade.php @@ -3,7 +3,7 @@ @csrf - + diff --git a/resources/views/competition/edit.blade.php b/resources/views/competition/edit.blade.php index aca09f4..359cecd 100644 --- a/resources/views/competition/edit.blade.php +++ b/resources/views/competition/edit.blade.php @@ -4,7 +4,7 @@ @method('PUT') - + diff --git a/resources/views/competition/index.blade.php b/resources/views/competition/index.blade.php index abdf60b..020aca8 100644 --- a/resources/views/competition/index.blade.php +++ b/resources/views/competition/index.blade.php @@ -1,3 +1,12 @@ + @foreach($competitions as $competition) +
+
{{ $competition->name }}
+
{{ $competition->description }}
+ +
+ @endforeach
diff --git a/resources/views/components/input.blade.php b/resources/views/components/input.blade.php index e75da34..288181a 100644 --- a/resources/views/components/input.blade.php +++ b/resources/views/components/input.blade.php @@ -1,5 +1,5 @@ -