all(); if (get_frontend_settings('recaptcha_status') == true && check_recaptcha($input['g-recaptcha-response']) == false) { Session::flash('error', get_phrase('Recaptcha verification failed')); return redirect(route('register.form')); } $validator = Validator::make($request->all(), [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'unique:users,email'], 'password' => ['required', Rules\Password::defaults()], ], [ 'name.required' => get_phrase('Name is required'), 'name.string' => get_phrase('Name must be a valid text'), 'name.max' => get_phrase('Name may not be greater than 255 characters'), 'email.required' => get_phrase('Email is required'), 'email.string' => get_phrase('Email must be a valid text'), 'email.email' => get_phrase('Please enter a valid email address'), 'email.unique' => get_phrase('This email is already registered. Please use a different email.'), 'password.required' => get_phrase('Password is required'), 'password.min' => get_phrase('Password must be at least 8 characters'), ]); if ($validator->fails()) { $firstError = $validator->errors()->first(); Session::flash('error', $firstError); return redirect()->back()->withErrors($validator)->withInput(); } // Check if the user is applying to be an instructor and validate instructor fields if ($request->has('instructor') && $request->instructor == 1) { // Validate instructor-specific fields (NIDN, Phone, Document) $instructorValidator = Validator::make($request->all(), [ 'nidn' => ['required', 'string', 'max:11'], 'phone' => ['required', 'string'], 'description' => ['nullable', 'string', 'max:1000'], 'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:2048'], ], [ 'nidn.required' => get_phrase('NIDN is required'), 'nidn.string' => get_phrase('NIDN must be a valid number'), 'nidn.max' => get_phrase('NIDN may not be greater than 11 characters'), 'phone.required' => get_phrase('Phone number is required'), 'description.max' => get_phrase('Description may not be greater than 1000 characters'), 'document.required' => get_phrase('Document is required'), 'document.file' => get_phrase('Document must be a valid file'), 'document.mimes' => get_phrase('Document must be PDF, DOC, or DOCX'), 'document.max' => get_phrase('Document size must be less than 2MB'), ]); if ($instructorValidator->fails()) { $firstError = $instructorValidator->errors()->first(); Session::flash('error', $firstError); return redirect()->back()->withErrors($instructorValidator)->withInput(); } } // Start database transaction for the entire registration process $user = null; try { DB::beginTransaction(); $user_data = [ 'name' => $request->name, 'email' => $request->email, 'role' => 'student', 'status' => 1, 'password' => Hash::make($request->password), ]; // Don't set email_verified_at here yet - only set it after everything succeeds // This prevents sending verification email if registration fails $user = User::create($user_data); // If applying as an instructor, process the application if ($request->has('instructor') && $request->instructor == 1) { $result = $this->processInstructorApplication($request, $user); // Check if result is a RedirectResponse with error if ($result instanceof RedirectResponse && $result->getSession()->get('error')) { DB::rollBack(); return $result; } // If result is a success response (true), continue with commit if ($result !== true) { DB::rollBack(); Session::flash('error', get_phrase('Error during registration. Please try again.')); return redirect()->back()->withInput(); } } // Now that everything succeeded, set email_verified_at if needed if (get_settings('student_email_verification') != 1) { $user->email_verified_at = Carbon::now(); $user->save(); // Save the update } // Fire the Registered event AFTER everything is committed // This ensures email is only sent when registration is fully successful event(new Registered($user)); DB::commit(); // Log the user in after successful registration Auth::login($user); return redirect(RouteServiceProvider::HOME); } catch (\Exception $e) { if (DB::transactionLevel() > 0) { DB::rollBack(); } Log::error('Registration error:', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'request' => $request->except(['password', 'document']) ]); Session::flash('error', get_phrase('Error during registration. Please try again.')); return redirect()->back()->withInput(); } } private function processInstructorApplication(Request $request, User $user) { try { // Check if application already exists if (Application::where('user_id', $user->id)->exists()) { Session::flash('error', get_phrase('Your request is in process. Please wait for admin to respond.')); return redirect()->back()->withInput(); } // Check if NIDN already exists in Instructors table with status = 1 (active) $nidn = $request->nidn; $existingInstructor = Instructors::where('nidn', $nidn) ->where('status', 1) ->first(); if ($existingInstructor) { Session::flash('error', get_phrase('This NIDN is already registered as an active instructor.')); return redirect()->back()->withInput(); } // Check NIDN with the API $api_url = "https://sindig.unesa.ac.id/apipddikti/api?nidn={$nidn}&auto=1"; Log::info('Calling API for NIDN: ' . $nidn); $response = Http::timeout(30)->get($api_url); if (!$response->successful()) { Log::error('API request failed', [ 'status' => $response->status(), 'body' => $response->body() ]); Session::flash('error', get_phrase('Unable to verify NIDN. Please try again later.')); return redirect()->back()->withInput(); } $data = $response->json(); Log::info('API Response for NIDN: ' . $nidn, ['response' => $data]); if (!isset($data['ok']) || $data['ok'] !== true || !isset($data['matched_dosen']) || count($data['matched_dosen']) == 0) { Session::flash('error', get_phrase('NIDN not found in the system. Please check your NIDN.')); return redirect()->back()->withInput(); } // Extract matched dosen data $matched_dosen = $data['matched_dosen'][0]; if (strtolower(trim($matched_dosen['nama'])) != strtolower(trim($user->name))) { Log::warning('Name mismatch', [ 'api_name' => $matched_dosen['nama'], 'user_name' => $user->name ]); Session::flash('error', get_phrase('Name does not match PDDikti records. Please check your name.')); return redirect()->back()->withInput(); } // Upload document $fileName = null; if ($request->hasFile('document') && $request->file('document')->isValid()) { $doc = $request->file('document'); $fileName = 'uploads/applications/' . $user->id . '_' . time() . '_' . Str::random(10) . '.' . $doc->getClientOriginalExtension(); $uploadResult = FileUploader::upload($doc, $fileName, null, null, 300); if (!$uploadResult) { Session::flash('error', get_phrase('Document upload failed. Please try again.')); return redirect()->back()->withInput(); } } else { Session::flash('error', get_phrase('Document upload failed or no document selected.')); return redirect()->back()->withInput(); } // Prepare instructor data $instructor = [ 'user_id' => $user->id, 'nidn' => $nidn, 'name' => $matched_dosen['nama'] ?? $user->name, 'id_sdm' => $matched_dosen['id'] ?? null, 'id_sms' => $matched_dosen['nama_prodi'] ?? null, 'id_pt' => $matched_dosen['nama_pt'] ?? null, 'status' => 0 ]; Log::info('Creating instructor record:', $instructor); // Prepare application data $application = [ 'user_id' => $user->id, 'phone' => $request->phone, 'description' => $request->description ?? null, 'document' => $fileName, 'status' => 0 ]; // Create both application and instructor records $applicationRecord = Application::create($application); Instructors::create($instructor); // Send notification to user try { $user->notify(new \App\Notifications\InstructorApplicant($applicationRecord)); } catch (\Exception $e) { Log::error('Failed to send notification:', ['error' => $e->getMessage()]); // Don't fail registration if notification fails } // Return true to indicate success return true; } catch (\Exception $e) { Log::error('Instructor application processing error:', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'user_id' => $user->id ?? null ]); Session::flash('error', get_phrase('Error processing instructor application. Please try again.')); return redirect()->back()->withInput(); } } }