first commit

This commit is contained in:
2026-06-10 10:46:22 +05:45
commit 473bdd627b
136 changed files with 19074 additions and 0 deletions
@@ -0,0 +1,47 @@
<?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\View\View;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create(): View
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('home', absolute: false));
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
@@ -0,0 +1,40 @@
<?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 Illuminate\View\View;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*/
public function show(): View
{
return view('auth.confirm-password');
}
/**
* 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,21 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*/
public function __invoke(Request $request): RedirectResponse|View
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
: view('auth.verify-email');
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
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 Illuminate\View\View;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*/
public function create(Request $request): View
{
return view('auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @throws 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 $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.
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class PasswordController extends Controller
{
/**
* Update the user's password.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return back()->with('status', 'password-updated');
}
}
@@ -0,0 +1,45 @@
<?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 Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*/
public function create(): View
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @throws ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => ['required', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$request->only('email')
);
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}
@@ -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 Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): View
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*
* @throws 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 redirect(route('dashboard', absolute: false));
}
}
@@ -0,0 +1,27 @@
<?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()) {
event(new Verified($request->user()));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
}
@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use App\Models\Registration;
use Illuminate\Http\Request;
class CommentController extends Controller
{
public function index(Request $request)
{
$request->validate(['registration_id' => 'required|exists:registrations,id']);
$comments = Comment::where('registration_id', $request->registration_id)
->orderBy('created_at', 'asc')
->get()
->map(fn($c) => [
'comment' => $c->comment,
'author' => auth()->user()->name,
'created_at_human' => $c->created_at->diffForHumans(),
]);
return response()->json(['comments' => $comments]);
}
public function store(Request $request)
{
$request->validate([
'registration_id' => 'required|exists:registrations,id',
'comment' => 'required|string|max:1000',
]);
Registration::findOrFail($request->registration_id);
$comment = Comment::create([
'registration_id' => $request->registration_id,
'comment' => $request->comment,
]);
return response()->json([
'comment' => [
'comment' => $comment->comment,
'author' => auth()->user()->name,
'created_at_human' => $comment->created_at->diffForHumans(),
]
]);
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace App\Http\Controllers;
use App\Models\Registration;
use Carbon\Carbon;
use Illuminate\Http\Request;
class HomeController extends Controller
{
public function index()
{
$user = auth()->user();
return match ($user->role) {
'admin' => $this->adminDashboard(),
'counselor' => $this->counselorDashboard(),
default => abort(403, 'Unauthorized action.'),
};
}
public function adminDashboard()
{
$today = Carbon::today();
$query = Registration::with([
'sessions' => function ($q) use ($today) {
$q->whereDate('play_date', $today)->with('shots');
}
])
->whereHas('sessions', function ($q) use ($today) {
$q->whereDate('play_date', $today);
})
->orderBy('created_at', 'desc')
->paginate(10);
$registrations = $query->getCollection()->map(function ($student) {
$todaySession = $student->sessions->first();
$todayGoals = null;
if ($todaySession) {
$todayGoals = $todaySession->shots
->sortBy('shot_number')
->map(fn($shot) => (bool) $shot->result)
->values()
->toArray();
}
return [
'id' => $student->id,
'name' => $student->name,
'phone' => $student->phone,
'email' => $student->email,
'today_goals' => $todayGoals,
'total_score' => $student->total_score,
'session_id' => $todaySession?->id,
];
});
$query->setCollection($registrations);
$data['registrations'] = $query;
$data['total'] = Registration::count();
$data['played'] = $registrations->filter(fn($r) => !empty($r['today_goals']) && count($r['today_goals']) >= 3)->count();
$data['topScore'] = $registrations->max('total_score') ?? 0;
return view('dashboard.admin', $data);
}
public function counselorDashboard()
{
$today = Carbon::today();
$query = Registration::with([
'sessions' => function ($q) use ($today) {
$q->whereDate('play_date', $today)->with('shots');
},
'comments'
])
->whereHas('sessions', function ($q) use ($today) {
$q->whereDate('play_date', $today);
})
->orderBy('created_at', 'desc')
->paginate(10);
$registrations = $query->getCollection()->map(function ($student) {
$todaySession = $student->sessions->first();
$todayGoals = null;
if ($todaySession) {
$todayGoals = $todaySession->shots
->sortBy('shot_number')
->map(fn($shot) => (bool) $shot->result)
->values()
->toArray();
}
return [
'id' => $student->id,
'name' => $student->name,
'phone' => $student->phone,
'email' => $student->email,
'today_goals' => $todayGoals,
'total_score' => $student->total_score,
'session_id' => $todaySession?->id,
'comment_count' => $student->comments->count(),
];
});
$query->setCollection($registrations);
$data['registrations'] = $query;
$data['total'] = Registration::count();
$data['played'] = $registrations->filter(fn($r) => !empty($r['today_goals']) && count($r['today_goals']) >= 3)->count();
$data['topScore'] = $registrations->max('total_score') ?? 0;
return view('dashboard.counselor', $data);
}
}
@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $request->user(),
]);
}
/**
* 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 Redirect::route('profile.edit')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}
@@ -0,0 +1,315 @@
<?php
namespace App\Http\Controllers;
use App\Models\GameSession;
use App\Models\GameShot;
use App\Models\Registration;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class RegistrationController extends Controller
{
public function index()
{
$registrations = Registration::orderBy('id', 'desc')->paginate(20);
return view('registrations', compact('registrations'));
}
public function create()
{
return view('registrations.create');
}
/*
|-------------------------------------------------
| STORE (ONLY USED AFTER OTP FOR NEW USERS)
|-------------------------------------------------
*/
public function store(Request $request)
{
$request->validate([
'phone' => 'required',
'name' => 'required',
'email' => 'nullable|email'
]);
$registration = Registration::create([
'name' => $request->name,
'email' => $request->email,
'phone' => $request->phone,
'total_score' => 0
]);
$session = GameSession::create([
'registration_id' => $registration->id,
'play_date' => today(),
'score' => 0,
]);
return response()->json([
'status' => 'session_created',
'registration' => [
'id' => $registration->id,
'name' => $registration->name,
'email' => $registration->email,
'phone' => $registration->phone,
'total_score' => 0,
'today_goals' => [],
'session_id' => $session->id,
]
]);
}
/*
|-------------------------------------------------
| SEND OTP
|-------------------------------------------------
*/
public function sendOtp(Request $request)
{
$request->validate([
'phone' => 'required'
]);
$phone = $request->phone;
$otp = rand(100000, 999999);
Cache::put("otp_$phone", $otp, now()->addMinutes(2));
return response()->json([
'status' => true,
'otp' => $otp // remove in production
]);
}
/*
|-------------------------------------------------
| VERIFY OTP (CORE LOGIC)
|-------------------------------------------------
*/
public function verifyOtp(Request $request)
{
$request->validate([
'phone' => 'required',
'otp' => 'required'
]);
$phone = $request->phone;
$cachedOtp = Cache::get("otp_$phone");
if (!$cachedOtp || $cachedOtp != $request->otp) {
return response()->json([
'status' => 'invalid_otp',
'message' => 'Invalid OTP'
], 422);
}
Cache::forget("otp_$phone");
$registration = Registration::where('phone', $phone)->first();
/*
| CASE 1: NEW USER
*/
if (!$registration) {
return response()->json([
'status' => 'needs_registration',
'phone' => $phone
]);
}
/*
| CASE 2: ALREADY PLAYED TODAY
*/
$todaySession = $registration->sessions()
->whereDate('play_date', today())
->first();
if ($todaySession && $todaySession->isComplete()) {
return response()->json([
'status' => 'blocked',
'message' => 'This student has already completed today\'s session.',
], 409);
}
// Session exists but shots not recorded yet — return it
if ($todaySession) {
return response()->json([
'status' => 'session_created',
'registration' => [
'id' => $registration->id,
'name' => $registration->name,
'email' => $registration->email,
'phone' => $registration->phone,
'total_score' => $registration->total_score,
'today_goals' => $todaySession->getShotArray(),
'session_id' => $todaySession->id,
]
]);
}
/*
| CASE 3: CREATE SESSION IMMEDIATELY
*/
$session = GameSession::create([
'registration_id' => $registration->id,
'play_date' => today(),
'score' => 0,
]);
return response()->json([
'status' => 'session_created',
'registration' => [
'id' => $registration->id,
'name' => $registration->name,
'email' => $registration->email,
'phone' => $registration->phone,
'total_score' => $registration->total_score,
'today_goals' => [],
'session_id' => $session->id,
]
]);
}
public function resendOtp(Request $request)
{
$request->validate([
'phone' => 'required'
]);
$phone = $request->phone;
$cooldownKey = 'otp_cooldown_' . $phone;
if (Cache::has($cooldownKey)) {
return response()->json([
'status' => false,
'message' => 'Please wait before resending OTP'
], 429);
}
$otp = rand(100000, 999999);
Cache::put("otp_$phone", $otp, now()->addMinutes(2));
Cache::put($cooldownKey, true, now()->addSeconds(30));
return response()->json([
'status' => true,
'message' => 'OTP resent successfully',
'otp' => $otp
]);
}
public function getRegistrationJson($id)
{
$registration = Registration::with('sessions.shots')->findOrFail($id);
return response()->json([
'registration' => [
'id' => $registration->id,
'name' => $registration->name,
'email' => $registration->email,
'phone' => $registration->phone,
'total_score' => $registration->total_score,
'today_goals' => optional(
$registration->sessions()->whereDate('play_date', today())->first()
)?->getShotArray()
]
]);
}
public function recordShot(Request $request)
{
$request->validate([
'session_id' => 'required|exists:game_sessions,id',
'shot_number' => 'required|integer|in:1,2,3',
'result' => 'required|boolean',
]);
$session = GameSession::with('registration')->findOrFail($request->session_id);
if ($session->shots()->where('shot_number', $request->shot_number)->exists()) {
return response()->json(['status' => 'error', 'message' => 'Shot already recorded.'], 409);
}
GameShot::create([
'game_session_id' => $session->id,
'shot_number' => $request->shot_number,
'result' => $request->result,
]);
$sessionScore = $session->calculateScore();
$session->update(['score' => $sessionScore]);
$registration = $session->registration;
$registration->update(['total_score' => $registration->sessions()->sum('score')]);
return response()->json([
'status' => 'ok',
'total_score' => $registration->fresh()->total_score,
]);
}
public function recordShots(Request $request)
{
$request->validate([
'session_id' => 'required|exists:game_sessions,id',
'shots' => 'required|array|size:3',
'shots.*' => 'boolean',
]);
$session = GameSession::with('registration')->findOrFail($request->session_id);
// Prevent re-recording
if ($session->isComplete()) {
return response()->json([
'status' => 'error',
'message' => 'Shots already recorded for this session.'
], 409);
}
// Store the 3 shots
foreach ($request->shots as $index => $result) {
GameShot::create([
'game_session_id' => $session->id,
'shot_number' => $index + 1,
'result' => $result,
]);
}
// Recalculate and update scores
$sessionScore = $session->calculateScore();
$session->update(['score' => $sessionScore]);
$registration = $session->registration;
$registration->increment('total_score', $sessionScore);
return response()->json([
'status' => 'ok',
'session_score' => $sessionScore,
'total_score' => $registration->fresh()->total_score,
]);
}
//Leaderboard
public function leaderboard()
{
$students = Registration::withCount('sessions')
->orderByDesc('total_score')
->orderBy('name')
->paginate(20);
$maxScore = Registration::max('total_score') ?? 0;
$total = Registration::count();
$data['students'] = $students;
$data['maxScore'] = $maxScore;
$data['total'] = $total;
return view('leaderboard', $data);
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use App\Models\Registration;
use Illuminate\Http\Request;
class ScoreboardController extends Controller
{
public function index()
{
return view('scoreboard');
}
public function select(Request $request)
{
$request->validate(['registration_id' => 'required|exists:registrations,id']);
session(['scoreboard_player_id' => $request->registration_id]);
return response()->json(['status' => 'ok']);
}
public function state()
{
$id = session('scoreboard_player_id');
if (!$id) {
return response()->json(['player' => null, 'leaderboard' => $this->leaderboard()]);
}
$reg = Registration::with([
'sessions' => fn($q) => $q->whereDate('play_date', today())->with('shots')
])->findOrFail($id);
$todaySession = $reg->sessions->first();
$shots = $todaySession
? $todaySession->shots->sortBy('shot_number')->map(fn($s) => (bool)$s->result)->values()->toArray()
: [];
return response()->json([
'player' => [
'id' => $reg->id,
'name' => $reg->name,
'total_score' => $reg->total_score,
'shots' => $shots,
'session_score' => $todaySession?->calculateScore() ?? 0,
],
'leaderboard' => $this->leaderboard(),
]);
}
private function leaderboard(): array
{
return Registration::orderByDesc('total_score')
->limit(5)
->get(['id', 'name', 'total_score'])
->map(fn($r, $i) => [
'rank' => $i + 1,
'name' => $r->name,
'total_score' => $r->total_score,
])
->toArray();
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Contracts\Validation\ValidationRule;
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, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @throws 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 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());
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
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, 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),
],
];
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = [
'registration_id',
'comment',
];
/*
|---------------------------
| Relationships
|---------------------------
*/
// Comment belongs to a student
public function registration()
{
return $this->belongsTo(Registration::class);
}
/*
|---------------------------
| Helper methods
|---------------------------
*/
// Optional: short preview for UI
public function getPreviewAttribute()
{
return strlen($this->comment) > 50
? substr($this->comment, 0, 50) . '...'
: $this->comment;
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GameSession extends Model
{
protected $fillable = [
'registration_id',
'play_date',
'score',
];
/*
|---------------------------
| Relationships
|---------------------------
*/
// Session belongs to a student
public function registration()
{
return $this->belongsTo(Registration::class);
}
// Session has many shots (1 session = 3 shots)
public function shots()
{
return $this->hasMany(GameShot::class);
}
/*
|---------------------------
| Helper methods
|---------------------------
*/
// Get score as array like [1,0,1]
public function getShotArray()
{
return $this->shots()
->orderBy('shot_number')
->pluck('result')
->toArray();
}
// Check if session is complete (3 shots done)
public function isComplete()
{
return $this->shots()->count() >= 3;
}
// Calculate score from shots (reliable source)
public function calculateScore()
{
return $this->shots()->where('result', 1)->count();
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GameShot extends Model
{
protected $fillable = [
'game_session_id',
'shot_number',
'result',
];
/*
|---------------------------
| Relationships
|---------------------------
*/
// Each shot belongs to a session
public function session()
{
return $this->belongsTo(GameSession::class, 'game_session_id');
}
/*
|---------------------------
| Helper methods
|---------------------------
*/
// Check if this shot is a goal
public function isGoal()
{
return (bool) $this->result;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Registration extends Model
{
protected $fillable = [
'name',
'email',
'phone',
'total_score',
];
/*
|---------------------------
| Relationships
|---------------------------
*/
// A student has many game sessions (daily plays)
public function sessions()
{
return $this->hasMany(GameSession::class);
}
// A student has many comments
public function comments()
{
return $this->hasMany(Comment::class);
}
/*
|---------------------------
| Helper methods
|---------------------------
*/
// Check if student already played today
public function playedToday(): bool
{
return $this->sessions()
->whereDate('play_date', today())
->whereHas('shots', fn($q) => $q->havingRaw('COUNT(*) >= 3'))
->exists();
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'role',
'email',
'password',
];
/**
* 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 isAdmin()
{
return $this->role === 'admin';
}
public function isCounselor()
{
return $this->role === 'counselor';
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.app');
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class GuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.guest');
}
}