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); } }