diff --git a/app/Http/Controllers/InterviewController.php b/app/Http/Controllers/InterviewController.php index 3fd65d6..9c2b7d6 100644 --- a/app/Http/Controllers/InterviewController.php +++ b/app/Http/Controllers/InterviewController.php @@ -102,6 +102,10 @@ public function loginCandidate(Request $request) return back()->with('error', 'Invalid candidate credentials or temporary password.'); } + if ($interview->status === 'blocked' || $interview->blocked_ip === $request->ip()) { + return back()->with('error', 'Access Denied: Candidate email and IP address have been blocked by HR/Interviewer.'); + } + if ($interview->isExpired()) { $interview->update(['status' => 'expired']); return back()->with('error', 'This candidate interview access window has expired. Please contact HR.'); @@ -124,6 +128,10 @@ public function showCandidateRoom($uniqueId) { $interview = Interview::where('submission_unique_id', $uniqueId)->firstOrFail(); + if ($interview->status === 'blocked') { + return response()->view('interview.expired', compact('interview'), 403); + } + if ($interview->isExpired()) { $interview->update(['status' => 'expired']); return response()->view('interview.expired', compact('interview'), 403); @@ -301,6 +309,60 @@ public function sendWarning(Request $request, $id) return response()->json(['success' => true, 'warning' => $warning]); } + /** + * Save Candidate Live Notepad Content. + */ + public function saveCandidateNotes(Request $request, $id) + { + $interview = Interview::findOrFail($id); + $interview->update(['candidate_notes' => $request->input('notes')]); + return response()->json(['success' => true]); + } + + /** + * Save Candidate Live Canvas Drawing Data URL. + */ + public function saveCandidateDrawing(Request $request, $id) + { + $interview = Interview::findOrFail($id); + $interview->update(['candidate_drawing' => $request->input('drawing')]); + return response()->json(['success' => true]); + } + + /** + * Regenerate Temporary Candidate Password (by Interviewer/HR). + */ + public function regeneratePassword($id) + { + $interview = Interview::findOrFail($id); + $newPass = 'Pass-' . rand(100000, 999999); + $interview->update(['temp_password' => $newPass]); + return response()->json(['success' => true, 'new_password' => $newPass]); + } + + /** + * Block Candidate Session, Email & Client IP (by Interviewer/HR). + */ + public function blockCandidate(Request $request, $id) + { + $interview = Interview::findOrFail($id); + $clientIp = $request->ip(); + + $interview->update([ + 'status' => 'blocked', + 'blocked_ip' => $clientIp, + ]); + + // Also block user record if exists + User::where('email', strtolower($interview->candidate_email)) + ->update(['is_blocked' => true]); + + return response()->json([ + 'success' => true, + 'message' => 'Candidate ' . $interview->candidate_name . ' (' . $interview->candidate_email . ') and IP ' . $clientIp . ' have been blocked successfully.' + ]); + } + /** * Live Polling Endpoint for Candidate Warnings & HR Monitoring. */ @@ -313,6 +375,10 @@ public function getPollData($id) 'submitted_code' => $interview->submitted_code, 'submitted_language' => $interview->submitted_language, 'code_output' => $interview->code_output, + 'candidate_notes' => $interview->candidate_notes, + 'candidate_drawing' => $interview->candidate_drawing, + 'temp_password' => $interview->temp_password, + 'blocked_ip' => $interview->blocked_ip, 'proctor_logs' => $interview->proctor_logs ?? [], 'warnings' => $interview->warnings, 'is_expired' => $interview->isExpired(), diff --git a/app/Models/Interview.php b/app/Models/Interview.php index 9f21e62..3df16d8 100644 --- a/app/Models/Interview.php +++ b/app/Models/Interview.php @@ -25,6 +25,9 @@ class Interview extends Model 'code_output', 'recording_path', 'submission_unique_id', + 'candidate_notes', + 'candidate_drawing', + 'blocked_ip', 'created_by', ]; diff --git a/database/migrations/2026_07_21_091740_add_notes_drawing_ip_to_interviews_table.php b/database/migrations/2026_07_21_091740_add_notes_drawing_ip_to_interviews_table.php new file mode 100644 index 0000000..1242801 --- /dev/null +++ b/database/migrations/2026_07_21_091740_add_notes_drawing_ip_to_interviews_table.php @@ -0,0 +1,30 @@ +text('candidate_notes')->nullable(); + $table->longText('candidate_drawing')->nullable(); + $table->string('blocked_ip')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('interviews', function (Blueprint $table) { + $table->dropColumn(['candidate_notes', 'candidate_drawing', 'blocked_ip']); + }); + } +}; diff --git a/resources/views/interview/candidate_room.blade.php b/resources/views/interview/candidate_room.blade.php index 3c68701..077487c 100644 --- a/resources/views/interview/candidate_room.blade.php +++ b/resources/views/interview/candidate_room.blade.php @@ -202,6 +202,14 @@