init commit
This commit is contained in:
commit
c9d982669a
461 changed files with 30317 additions and 0 deletions
51
app/Console/Commands/MakeAdmin.php
Normal file
51
app/Console/Commands/MakeAdmin.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class MakeAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:make-admin {email : The email address of the user to make admin}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Makes someone an admin based on their email address';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$email = $this->argument('email');
|
||||
|
||||
// Find the user by email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (!$user) {
|
||||
$this->error("User with email {$email} not found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check if user is already an admin
|
||||
if ($user->hasRole('admin')) {
|
||||
$this->info("User {$email} is already an admin.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Assign admin role
|
||||
$user->assignRole('admin');
|
||||
|
||||
$this->info("User {$email} has been made an admin successfully.");
|
||||
return 0;
|
||||
}
|
||||
}
|
51
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
51
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the login page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/Login', [
|
||||
'canResetPassword' => Route::has('password.request'),
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password page.
|
||||
*/
|
||||
public function show(): Response
|
||||
{
|
||||
return Inertia::render('auth/ConfirmPassword');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the email verification prompt page.
|
||||
*/
|
||||
public function __invoke(Request $request): RedirectResponse|Response
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: Inertia::render('auth/VerifyEmail', ['status' => $request->session()->get('status')]);
|
||||
}
|
||||
}
|
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the password reset page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/ResetPassword', [
|
||||
'email' => $request->email,
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status == Password::PasswordReset) {
|
||||
return to_route('login')->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [__($status)],
|
||||
]);
|
||||
}
|
||||
}
|
41
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
41
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the password reset link request page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/ForgotPassword', [
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
return back()->with('status', __('A reset link will be sent if the account exists.'));
|
||||
}
|
||||
}
|
51
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
51
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the registration page.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('auth/Register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return to_route('dashboard');
|
||||
}
|
||||
}
|
29
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
29
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
/** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */
|
||||
$user = $request->user();
|
||||
event(new Verified($user));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
119
app/Http/Controllers/CompetitionController.php
Normal file
119
app/Http/Controllers/CompetitionController.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Competition;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class CompetitionController extends Controller
|
||||
{
|
||||
|
||||
public static function getPublicCompetitions(int $skip = 0, int $take = 10): JsonResponse
|
||||
{
|
||||
if ($skip < 0) {
|
||||
$skip = 0;
|
||||
}
|
||||
if ($take < 1 || $take > 100) {
|
||||
$take = 10;
|
||||
}
|
||||
|
||||
$competitions = Competition::where('status', 'public')
|
||||
->orderBy('start_date', 'desc')
|
||||
->skip($skip)
|
||||
->take($take)
|
||||
->get();
|
||||
|
||||
$total = Competition::where('status', 'public')->count();
|
||||
|
||||
return response()->json([
|
||||
'data' => $competitions,
|
||||
'meta' => [
|
||||
'skip' => $skip,
|
||||
'take' => $take,
|
||||
'total' => $total,
|
||||
'hasMore' => ($skip + $take) < $total
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all public competitions/tournaments with pagination.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getPublicCompetitions(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'skip' => 'integer|min:0',
|
||||
'take' => 'integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
$skip = $request->input('skip', 0);
|
||||
$take = $request->input('take', 10);
|
||||
|
||||
$competitions = Competition::where('status', 'public')
|
||||
->orderBy('start_date', 'desc')
|
||||
->skip($skip)
|
||||
->take($take)
|
||||
->get();
|
||||
|
||||
$total = Competition::where('status', 'public')->count();
|
||||
|
||||
return response()->json([
|
||||
'data' => $competitions,
|
||||
'meta' => [
|
||||
'skip' => $skip,
|
||||
'take' => $take,
|
||||
'total' => $total,
|
||||
'hasMore' => ($skip + $take) < $total
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get competitions/tournaments for the authenticated user with pagination.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getUserCompetitions(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'skip' => 'integer|min:0',
|
||||
'take' => 'integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
$skip = $request->input('skip', 0);
|
||||
$take = $request->input('take', 10);
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'Unauthenticated'], 401);
|
||||
}
|
||||
|
||||
$query = Competition::whereHas('teams', function ($query) use ($user) {
|
||||
$query->whereHas('users', function ($query) use ($user) {
|
||||
$query->where('users.id', $user->id);
|
||||
});
|
||||
});
|
||||
|
||||
$competitions = $query->orderBy('start_date', 'desc')
|
||||
->skip($skip)
|
||||
->take($take)
|
||||
->get();
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
return response()->json([
|
||||
'data' => $competitions,
|
||||
'meta' => [
|
||||
'skip' => $skip,
|
||||
'take' => $take,
|
||||
'total' => $total,
|
||||
'hasMore' => ($skip + $take) < $total
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
40
app/Http/Controllers/Settings/PasswordController.php
Normal file
40
app/Http/Controllers/Settings/PasswordController.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's password settings page.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('settings/Password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
22
app/Http/Controllers/Settings/PermissionsController.php
Normal file
22
app/Http/Controllers/Settings/PermissionsController.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Permission;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class PermissionsController extends Controller
|
||||
{
|
||||
public function show(User $user)
|
||||
{
|
||||
$permissions = $user->permissions()->all();
|
||||
Log::info($permissions);
|
||||
return Inertia::render('Settings/Permissions', [
|
||||
'permissions' => $permissions
|
||||
]);
|
||||
}
|
||||
}
|
63
app/Http/Controllers/Settings/ProfileController.php
Normal file
63
app/Http/Controllers/Settings/ProfileController.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's profile settings page.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('settings/Profile', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return to_route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's profile.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
34
app/Http/Middleware/CheckPermission.php
Normal file
34
app/Http/Middleware/CheckPermission.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CheckPermission
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$permissions)
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
// Check if user has any of the required permissions
|
||||
foreach ($permissions as $permission) {
|
||||
if ($user->hasPermission($permission)) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
// If no permissions match, return 403
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
}
|
33
app/Http/Middleware/CheckRole.php
Normal file
33
app/Http/Middleware/CheckRole.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckRole
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$roles)
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
foreach ($roles as $role) {
|
||||
if ($user->hasRole($role)) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
}
|
23
app/Http/Middleware/HandleAppearance.php
Normal file
23
app/Http/Middleware/HandleAppearance.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HandleAppearance
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
View::share('appearance', $request->cookie('appearance') ?? 'system');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
59
app/Http/Middleware/HandleInertiaRequests.php
Normal file
59
app/Http/Middleware/HandleInertiaRequests.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Middleware;
|
||||
use Tighten\Ziggy\Ziggy;
|
||||
|
||||
class HandleInertiaRequests extends Middleware
|
||||
{
|
||||
/**
|
||||
* The root template that's loaded on the first page visit.
|
||||
*
|
||||
* @see https://inertiajs.com/server-side-setup#root-template
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rootView = 'app';
|
||||
|
||||
/**
|
||||
* Determines the current asset version.
|
||||
*
|
||||
* @see https://inertiajs.com/asset-versioning
|
||||
*/
|
||||
public function version(Request $request): ?string
|
||||
{
|
||||
return parent::version($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the props that are shared by default.
|
||||
*
|
||||
* @see https://inertiajs.com/shared-data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function share(Request $request): array
|
||||
{
|
||||
[$message, $author] = str(Inspiring::quotes()->random())->explode('-');
|
||||
$user = $request->user();
|
||||
$isAdmin = $user && $user->hasRole('admin');
|
||||
|
||||
return [
|
||||
...parent::share($request),
|
||||
'name' => config('app.name'),
|
||||
'quote' => ['message' => trim($message), 'author' => trim($author)],
|
||||
'auth' => [
|
||||
'user' => $user,
|
||||
'isAdmin' => $isAdmin,
|
||||
],
|
||||
'ziggy' => [
|
||||
...(new Ziggy)->toArray(),
|
||||
'location' => $request->url(),
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
];
|
||||
}
|
||||
}
|
85
app/Http/Requests/Auth/LoginRequest.php
Normal file
85
app/Http/Requests/Auth/LoginRequest.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the request's credentials.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($this->throttleKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the login request is not rate limited.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function ensureIsNotRateLimited(): void
|
||||
{
|
||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event(new Lockout($this));
|
||||
|
||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiting throttle key for the request.
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
}
|
||||
}
|
30
app/Http/Requests/Settings/ProfileUpdateRequest.php
Normal file
30
app/Http/Requests/Settings/ProfileUpdateRequest.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
66
app/Models/BreakPeriod.php
Normal file
66
app/Models/BreakPeriod.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class BreakPeriod extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'competition_id',
|
||||
'field_id',
|
||||
'name',
|
||||
'description',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'type',
|
||||
'status',
|
||||
'round',
|
||||
'match_slot',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'start_time' => 'datetime',
|
||||
'end_time' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the competition that this break period belongs to.
|
||||
*/
|
||||
public function competition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Competition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field that this break period belongs to.
|
||||
*/
|
||||
public function field(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Field::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the teams that are on break during this period.
|
||||
*/
|
||||
public function teams(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Team::class)
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
90
app/Models/Competition.php
Normal file
90
app/Models/Competition.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Competition extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'status',
|
||||
'location',
|
||||
'max_teams',
|
||||
'current_scheduling_mode_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the teams participating in the competition.
|
||||
*/
|
||||
public function teams(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Team::class)
|
||||
->withTimestamps()
|
||||
->withPivot('status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheduling modes available for this competition.
|
||||
*/
|
||||
public function schedulingModes(): HasMany
|
||||
{
|
||||
return $this->hasMany(SchedulingMode::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current scheduling mode for this competition.
|
||||
*/
|
||||
public function currentSchedulingMode()
|
||||
{
|
||||
return $this->belongsTo(SchedulingMode::class, 'current_scheduling_mode_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matches for this competition.
|
||||
*/
|
||||
public function matches(): HasMany
|
||||
{
|
||||
return $this->hasMany(MatchGame::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the break periods for this competition.
|
||||
*/
|
||||
public function breakPeriods(): HasMany
|
||||
{
|
||||
return $this->hasMany(BreakPeriod::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields used in this competition.
|
||||
*/
|
||||
public function fields(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Field::class)
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
64
app/Models/Field.php
Normal file
64
app/Models/Field.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Field extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'location',
|
||||
'description',
|
||||
'status',
|
||||
'capacity',
|
||||
'surface_type',
|
||||
'indoor',
|
||||
'dimensions',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'indoor' => 'boolean',
|
||||
'dimensions' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the competitions that use this field.
|
||||
*/
|
||||
public function competitions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Competition::class)
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matches scheduled on this field.
|
||||
*/
|
||||
public function matches(): HasMany
|
||||
{
|
||||
return $this->hasMany(MatchGame::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the break periods scheduled on this field.
|
||||
*/
|
||||
public function breakPeriods(): HasMany
|
||||
{
|
||||
return $this->hasMany(BreakPeriod::class);
|
||||
}
|
||||
}
|
105
app/Models/MatchGame.php
Normal file
105
app/Models/MatchGame.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MatchGame extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'matches';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'competition_id',
|
||||
'scheduling_mode_id',
|
||||
'home_team_id',
|
||||
'away_team_id',
|
||||
'field_id',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'home_team_score',
|
||||
'away_team_score',
|
||||
'status',
|
||||
'round',
|
||||
'group',
|
||||
'match_number',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'start_time' => 'datetime',
|
||||
'end_time' => 'datetime',
|
||||
'home_team_score' => 'integer',
|
||||
'away_team_score' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the competition that the match belongs to.
|
||||
*/
|
||||
public function competition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Competition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheduling mode that this match is part of.
|
||||
*/
|
||||
public function schedulingMode(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SchedulingMode::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the home team.
|
||||
*/
|
||||
public function homeTeam(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class, 'home_team_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the away team.
|
||||
*/
|
||||
public function awayTeam(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class, 'away_team_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field where the match is played.
|
||||
*/
|
||||
public function field(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Field::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this match is during a break period.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBankPeriod()
|
||||
{
|
||||
return BreakPeriod::where('competition_id', $this->competition_id)
|
||||
->where('start_time', '<=', $this->start_time)
|
||||
->where('end_time', '>=', $this->end_time)
|
||||
->exists();
|
||||
}
|
||||
}
|
42
app/Models/Permission.php
Normal file
42
app/Models/Permission.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Permission extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'display_name',
|
||||
'description',
|
||||
'is_wildcard'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_wildcard' => 'boolean'
|
||||
];
|
||||
|
||||
public function roles()
|
||||
{
|
||||
return $this->belongsToMany(Role::class)->withpivot('granted')->withTimestamps();
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class)->withPivot('granted')->withTimestamps();
|
||||
}
|
||||
|
||||
public function matches($permission)
|
||||
{
|
||||
if (!$this->is_wildcard) {
|
||||
return $this->name === $permission;
|
||||
}
|
||||
|
||||
$pattern = str_replace('*', '.*', $this->name);
|
||||
return preg_match('/^' . $pattern . '$/', $permission);
|
||||
}
|
||||
}
|
47
app/Models/Role.php
Normal file
47
app/Models/Role.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'display_name',
|
||||
'description'
|
||||
];
|
||||
|
||||
public function permissions()
|
||||
{
|
||||
return $this->belongsToMany(Permission::class)->withPivot('granted')->withTimestamps();
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class)->withTimestamps();
|
||||
}
|
||||
|
||||
public function hasPermission($permission)
|
||||
{
|
||||
return $this->permissions->where('pivot.granted', true)->contains('name', $permission) ||
|
||||
$this->hasWildcardPermission($permission);
|
||||
}
|
||||
|
||||
private function hasWildcardPermission($permission)
|
||||
{
|
||||
$wildcardPermissions = $this->permissions->where('is_wildcard', true)->where('pivot.granted', true);
|
||||
|
||||
foreach ($wildcardPermissions as $wildcardPermission) {
|
||||
$pattern = str_replace('*', '.*', $wildcardPermission->name);
|
||||
if (preg_match('/^' . $pattern . '$/', $permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
128
app/Models/SchedulingMode.php
Normal file
128
app/Models/SchedulingMode.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class SchedulingMode extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'competition_id',
|
||||
'name',
|
||||
'description',
|
||||
'algorithm',
|
||||
'config',
|
||||
'sequence_order',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'config' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the competition that this scheduling mode belongs to.
|
||||
*/
|
||||
public function competition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Competition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matches associated with this scheduling mode.
|
||||
*/
|
||||
public function matches(): HasMany
|
||||
{
|
||||
return $this->hasMany(MatchGame::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get competitions that are currently using this scheduling mode.
|
||||
*/
|
||||
public function activeCompetitions(): HasMany
|
||||
{
|
||||
return $this->hasMany(Competition::class, 'current_scheduling_mode_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is the current active scheduling mode for its competition.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->competition->current_scheduling_mode_id === $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate match schedule based on the algorithm defined.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function generateSchedule(): array
|
||||
{
|
||||
// This would contain the logic to delegate to different scheduling algorithms
|
||||
// based on the 'algorithm' field
|
||||
switch ($this->algorithm) {
|
||||
case 'round_robin':
|
||||
return $this->generateRoundRobinSchedule();
|
||||
|
||||
case 'knockout':
|
||||
return $this->generateKnockoutSchedule();
|
||||
|
||||
case 'group_stage':
|
||||
return $this->generateGroupStageSchedule();
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example implementation for round-robin scheduling.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateRoundRobinSchedule(): array
|
||||
{
|
||||
// Implementation would go here
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Example implementation for knockout scheduling.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateKnockoutSchedule(): array
|
||||
{
|
||||
// Implementation would go here
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Example implementation for group stage scheduling.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateGroupStageSchedule(): array
|
||||
{
|
||||
// Implementation would go here
|
||||
return [];
|
||||
}
|
||||
}
|
70
app/Models/Team.php
Normal file
70
app/Models/Team.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Team extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'logo',
|
||||
'nb_players',
|
||||
'contact_email',
|
||||
'contact_phone',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the competitions this team is participating in.
|
||||
*/
|
||||
public function competitions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Competition::class)
|
||||
->withTimestamps()
|
||||
->withPivot('status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matches where this team is the home team.
|
||||
*/
|
||||
public function homeMatches(): HasMany
|
||||
{
|
||||
return $this->hasMany(MatchGame::class, 'home_team_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matches where this team is the away team.
|
||||
*/
|
||||
public function awayMatches(): HasMany
|
||||
{
|
||||
return $this->hasMany(MatchGame::class, 'away_team_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all matches for this team (both home and away).
|
||||
*/
|
||||
public function matches()
|
||||
{
|
||||
return $this->homeMatches->merge($this->awayMatches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the break periods assigned to this team.
|
||||
*/
|
||||
public function breakPeriods(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(BreakPeriod::class)
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
158
app/Models/User.php
Normal file
158
app/Models/User.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, HasUuids;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'avatar',
|
||||
'oidc_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function roles()
|
||||
{
|
||||
return $this->belongsToMany(Role::class)->withTimestamps();
|
||||
}
|
||||
|
||||
public function permissions()
|
||||
{
|
||||
return $this->belongsToMany(Permission::class)->withPivot('granted')->withTimestamps();
|
||||
}
|
||||
|
||||
public function hasRole($role)
|
||||
{
|
||||
if (is_string($role)) {
|
||||
return $this->roles->contains('name', $role);
|
||||
}
|
||||
|
||||
return $this->roles->contains($role);
|
||||
}
|
||||
|
||||
public function hasAnyRole($roles)
|
||||
{
|
||||
if (is_array($roles)) {
|
||||
return $this->roles->whereIn('name', $roles)->isNotEmpty();
|
||||
}
|
||||
|
||||
return $this->hasRole($roles);
|
||||
}
|
||||
|
||||
public function hasPermission($permission)
|
||||
{
|
||||
// Check direct permissions
|
||||
if ($this->hasDirectPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check role-based permissions
|
||||
return $this->hasRolePermission($permission);
|
||||
}
|
||||
|
||||
private function hasDirectPermission($permission)
|
||||
{
|
||||
// Check exact match with granted
|
||||
if ($this->permissions->where('pivot.granted', true)->contains('name', $permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check wildcard permissions with granted
|
||||
$wildcardPermissions = $this->permissions->where('is_wildcard', true)->where('pivot.granted', true);
|
||||
|
||||
foreach ($wildcardPermissions as $wildcardPermission) {
|
||||
if ($wildcardPermission->matches($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hasRolePermission($permission)
|
||||
{
|
||||
foreach ($this->roles as $role) {
|
||||
if ($role->hasPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function assignRole($role)
|
||||
{
|
||||
if (is_string($role)) {
|
||||
$role = Role::where('name', $role)->firstOrFail();
|
||||
}
|
||||
|
||||
$this->roles()->syncWithoutDetaching([$role->id]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeRole($role)
|
||||
{
|
||||
if (is_string($role)) {
|
||||
$role = Role::where('name', $role)->firstOrFail();
|
||||
}
|
||||
|
||||
$this->roles()->detach($role->id);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function givePermission($permission)
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
$permission = Permission::where('name', $permission)->firstOrFail();
|
||||
}
|
||||
|
||||
$this->permissions()->syncWithoutDetaching([$permission->id => ['granted' => true]]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function revokePermission($permission)
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
$permission = Permission::where('name', $permission)->firstOrFail();
|
||||
}
|
||||
|
||||
$this->permissions()->updateExistingPivot($permission->id, ['granted' => false]);
|
||||
return $this;
|
||||
}
|
||||
}
|
55
app/Notifications/ForgotPassword.php
Normal file
55
app/Notifications/ForgotPassword.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ForgotPassword extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct(public string $passwordResetCode) {}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*/
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
// ->from('')
|
||||
->subject('Reset your password')
|
||||
->line('Use the code below to reset your password')
|
||||
->line($this->passwordResetCode)
|
||||
->line('If this is not you, please feel free to ignore this message')
|
||||
->line('Thank you');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'message' => "Use this code to reset your password: {$this->passwordResetCode}",
|
||||
'title' => 'Reset your password',
|
||||
];
|
||||
}
|
||||
}
|
69
app/Providers/AppServiceProvider.php
Normal file
69
app/Providers/AppServiceProvider.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
|
||||
$event->extendSocialite('keycloak', \SocialiteProviders\Keycloak\Provider::class);
|
||||
});
|
||||
|
||||
Blade::if('permission', function ($permission) {
|
||||
return Auth::check() && Auth::user()->hasPermission($permission);
|
||||
});
|
||||
|
||||
Blade::if('role', function ($role) {
|
||||
return Auth::check() && Auth::user()->hasRole($role);
|
||||
});
|
||||
|
||||
Blade::if('anyrole', function (...$roles) {
|
||||
return Auth::check() && Auth::user()->hasAnyRole($roles);
|
||||
});
|
||||
|
||||
Blade::if('allpermissions', function (...$permissions) {
|
||||
if (!Auth::check()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (!Auth::user()->hasPermission($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Blade::if('anypermission', function (...$permissions) {
|
||||
if (!Auth::check()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (Auth::user()->hasPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
117
app/Services/PermissionService.php
Normal file
117
app/Services/PermissionService.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
|
||||
class PermissionService
|
||||
{
|
||||
public function createPermission($name, $displayName = null, $description = null)
|
||||
{
|
||||
$isWildcard = str_contains($name, '*');
|
||||
|
||||
return Permission::create([
|
||||
'name' => $name,
|
||||
'display_name' => $displayName ?? $name,
|
||||
'description' => $description,
|
||||
'is_wildcard' => $isWildcard
|
||||
]);
|
||||
}
|
||||
|
||||
public function createRole($name, $displayName = null, $description = null)
|
||||
{
|
||||
return Role::create([
|
||||
'name' => $name,
|
||||
'display_name' => $displayName ?? $name,
|
||||
'description' => $description
|
||||
]);
|
||||
}
|
||||
|
||||
public function assignPermissionToRole($permission, $role)
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
$permission = Permission::where('name', $permission)->firstOrFail();
|
||||
}
|
||||
|
||||
if (is_string($role)) {
|
||||
$role = Role::where('name', $role)->firstOrFail();
|
||||
}
|
||||
|
||||
$role->permissions()->syncWithoutDetaching([$permission->id]);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
public function removePermissionFromRole($permission, $role)
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
$permission = Permission::where('name', $permission)->firstOrFail();
|
||||
}
|
||||
|
||||
if (is_string($role)) {
|
||||
$role = Role::where('name', $role)->firstOrFail();
|
||||
}
|
||||
|
||||
$role->permissions()->detach($permission->id);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
public function getUserPermissions(User $user)
|
||||
{
|
||||
// Get direct permissions
|
||||
$directPermissions = $user->permissions;
|
||||
|
||||
// Get role-based permissions
|
||||
$rolePermissions = collect();
|
||||
foreach ($user->roles as $role) {
|
||||
$rolePermissions = $rolePermissions->merge($role->permissions);
|
||||
}
|
||||
|
||||
// Merge and remove duplicates
|
||||
return $directPermissions->merge($rolePermissions)->unique('id');
|
||||
}
|
||||
|
||||
public function checkPermission(User $user, $permission)
|
||||
{
|
||||
return $user->hasPermission($permission);
|
||||
}
|
||||
|
||||
public function getMatchingPermissions($permissionPattern)
|
||||
{
|
||||
$allPermissions = Permission::all();
|
||||
$matchingPermissions = collect();
|
||||
|
||||
foreach ($allPermissions as $perm) {
|
||||
if ($perm->is_wildcard && $perm->matches($permissionPattern)) {
|
||||
$matchingPermissions->push($perm);
|
||||
} elseif ($perm->name === $permissionPattern) {
|
||||
$matchingPermissions->push($perm);
|
||||
}
|
||||
}
|
||||
|
||||
return $matchingPermissions;
|
||||
}
|
||||
|
||||
public function syncRolePermissions(Role $role, array $permissions)
|
||||
{
|
||||
$permissionIds = [];
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (is_string($permission)) {
|
||||
$perm = Permission::where('name', $permission)->first();
|
||||
if ($perm) {
|
||||
$permissionIds[] = $perm->id;
|
||||
}
|
||||
} else {
|
||||
$permissionIds[] = $permission->id;
|
||||
}
|
||||
}
|
||||
|
||||
$role->permissions()->sync($permissionIds);
|
||||
|
||||
return $role;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue