feat: Implement National ID sub-account login flow for students
- Updates verifyOtp to return identity_token for students instead of auto-login - Adds verifyNationalId API endpoint to select or create a student profile under a single phone number - Allows multiple students per guardian phone number
This commit is contained in:
@@ -254,27 +254,22 @@ class AuthController
|
|||||||
'school_id' => null
|
'school_id' => null
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
// Student Role
|
// Student Role - Requires National ID Phase (Netflix-style Profile Selection via National ID)
|
||||||
$student = Database::selectOne("SELECT * FROM students WHERE identity_id = ? LIMIT 1", [$identityId]);
|
$identityToken = Security::generateJWT([
|
||||||
if (!$student) {
|
'identity_id' => $identityId,
|
||||||
$sUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
'role' => 'student_identity_pending',
|
||||||
$natId = 'NAT' . substr($cleanPhone, -8);
|
'phone' => $cleanPhone,
|
||||||
$sId = Database::insert(
|
], 3600); // 1 hour validity
|
||||||
"INSERT INTO students (uuid, identity_id, national_id, full_name, grade_level, stream, is_school_sponsored)
|
|
||||||
VALUES (?, ?, ?, ?, 'tawjihi_2008', 'scientific', 0)",
|
$response->status(200)->json([
|
||||||
[$sUuid, $identityId, $natId, $resolvedName]
|
'status' => 'success',
|
||||||
);
|
'message' => 'تم التحقق من رقم الهاتف بنجاح. يرجى إدخال الرقم الوطني للمتابعة.',
|
||||||
$student = ['id' => $sId, 'uuid' => $sUuid, 'full_name' => $resolvedName, 'school_id' => null];
|
'data' => [
|
||||||
}
|
'identity_token' => $identityToken,
|
||||||
$user = [
|
'requires_national_id' => true
|
||||||
'id' => $student['id'],
|
]
|
||||||
'uuid' => $student['uuid'],
|
]);
|
||||||
'full_name' => $student['full_name'],
|
return;
|
||||||
'role' => 'student',
|
|
||||||
'status' => 'active',
|
|
||||||
'token_version' => $identity['token_version'],
|
|
||||||
'school_id' => $student['school_id'] ?? null
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Register / Update Device Fingerprint in user_devices
|
// 3. Register / Update Device Fingerprint in user_devices
|
||||||
@@ -461,6 +456,67 @@ class AuthController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify Student National ID (Profile Selection / Sub-account Login)
|
||||||
|
* POST /api/auth/student/login-national-id
|
||||||
|
*/
|
||||||
|
public function verifyNationalId(Request $request, Response $response): void
|
||||||
|
{
|
||||||
|
$body = $request->getJSON();
|
||||||
|
$identityToken = $body['identity_token'] ?? '';
|
||||||
|
$nationalId = trim((string)($body['national_id'] ?? ''));
|
||||||
|
|
||||||
|
if (!$identityToken || !$nationalId) {
|
||||||
|
$response->status(400)->json(['status' => 'error', 'message' => 'الرقم الوطني مطلوب']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = Security::verifyJWT($identityToken);
|
||||||
|
if (!$decoded || ($decoded->role ?? '') !== 'student_identity_pending') {
|
||||||
|
$response->status(401)->json(['status' => 'error', 'message' => 'الجلسة غير صالحة، يرجى إعادة التحقق من رقم الهاتف']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$identityId = (int)$decoded->identity_id;
|
||||||
|
|
||||||
|
// Check if student exists with this National ID
|
||||||
|
$student = Database::selectOne("SELECT * FROM students WHERE national_id = ? LIMIT 1", [$nationalId]);
|
||||||
|
|
||||||
|
if ($student) {
|
||||||
|
// Student exists. Verify identity linkage.
|
||||||
|
if ($student['identity_id'] === null) {
|
||||||
|
// Pre-registered by Guardian, link to this identity phone now
|
||||||
|
Database::query("UPDATE students SET identity_id = ? WHERE id = ?", [$identityId, $student['id']]);
|
||||||
|
} elseif ((int)$student['identity_id'] !== $identityId) {
|
||||||
|
$response->status(403)->json(['status' => 'error', 'message' => 'الرقم الوطني مسجل ومربوط برقم هاتف آخر. يرجى مراجعة الدعم الفني.']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate full student session!
|
||||||
|
$this->generateSessionAndRespond(
|
||||||
|
(int)$student['id'],
|
||||||
|
$student['uuid'],
|
||||||
|
'student',
|
||||||
|
'browser_default',
|
||||||
|
$decoded->phone,
|
||||||
|
$student['full_name'],
|
||||||
|
$response,
|
||||||
|
'تم تسجيل الدخول لملف الطالب بنجاح'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// New Student! Forward to Onboarding.
|
||||||
|
$response->status(200)->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'الرقم الوطني غير مسجل مسبقاً، يرجى استكمال البيانات لفتح ملف جديد.',
|
||||||
|
'data' => [
|
||||||
|
'requires_onboarding' => true,
|
||||||
|
'identity_token' => $identityToken,
|
||||||
|
'national_id' => $nationalId
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if Student Profile is complete
|
* Check if Student Profile is complete
|
||||||
* GET /api/student/profile/status
|
* GET /api/student/profile/status
|
||||||
@@ -501,38 +557,78 @@ class AuthController
|
|||||||
*/
|
*/
|
||||||
public function studentProfileSetup(Request $request, Response $response): void
|
public function studentProfileSetup(Request $request, Response $response): void
|
||||||
{
|
{
|
||||||
$studentId = (int)$request->user_id;
|
|
||||||
$body = $request->getBody();
|
$body = $request->getBody();
|
||||||
|
$identityToken = $body['identity_token'] ?? '';
|
||||||
|
|
||||||
$fullName = trim((string)($body['full_name'] ?? ''));
|
$fullName = trim((string)($body['full_name'] ?? ''));
|
||||||
$gradeLevel = trim((string)($body['grade_level'] ?? 'grade_10'));
|
$gradeLevel = trim((string)($body['grade_level'] ?? 'grade_10'));
|
||||||
$stream = trim((string)($body['stream'] ?? 'scientific'));
|
$stream = trim((string)($body['stream'] ?? 'scientific'));
|
||||||
$nationalId = trim((string)($body['national_id'] ?? ''));
|
$nationalId = trim((string)($body['national_id'] ?? ''));
|
||||||
|
|
||||||
if (empty($fullName)) {
|
if (empty($fullName) || empty($nationalId)) {
|
||||||
$response->status(400)->json(['status' => 'error', 'message' => 'الاسم الكامل مطلوب']);
|
$response->status(400)->json(['status' => 'error', 'message' => 'الاسم الكامل والرقم الوطني مطلوبان']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::query(
|
if ($identityToken) {
|
||||||
"UPDATE students SET full_name = ?, grade_level = ?, stream = ?, national_id = IF(? != '', ?, national_id), updated_at = NOW() WHERE id = ?",
|
// New Student Flow
|
||||||
[$fullName, $gradeLevel, $stream, $nationalId, $nationalId, $studentId]
|
$decoded = Security::verifyJWT($identityToken);
|
||||||
);
|
if (!$decoded || ($decoded->role ?? '') !== 'student_identity_pending') {
|
||||||
|
$response->status(401)->json(['status' => 'error', 'message' => 'الجلسة غير صالحة']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$identityId = (int)$decoded->identity_id;
|
||||||
|
|
||||||
$student = Database::selectOne("SELECT * FROM students WHERE id = ? LIMIT 1", [$studentId]);
|
// Ensure National ID doesn't exist
|
||||||
|
$existing = Database::selectOne("SELECT id FROM students WHERE national_id = ? LIMIT 1", [$nationalId]);
|
||||||
|
if ($existing) {
|
||||||
|
$response->status(400)->json(['status' => 'error', 'message' => 'الرقم الوطني مستخدم مسبقاً']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$response->json([
|
$sUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||||
'status' => 'success',
|
|
||||||
'message' => 'تم استكمال ملف الطالب بنجاح! مرحباً بك في منصة صَقِل',
|
$sId = Database::insert(
|
||||||
'user' => [
|
"INSERT INTO students (uuid, identity_id, national_id, full_name, grade_level, stream, is_school_sponsored)
|
||||||
'id' => $student['id'],
|
VALUES (?, ?, ?, ?, ?, ?, 0)",
|
||||||
'uuid' => $student['uuid'],
|
[$sUuid, $identityId, $nationalId, $fullName, $gradeLevel, $stream]
|
||||||
'full_name' => $student['full_name'],
|
);
|
||||||
'grade_level' => $student['grade_level'],
|
|
||||||
'stream' => $student['stream'],
|
// Auto-link to Guardian if exists on this phone
|
||||||
'role' => 'student'
|
$guardian = Database::selectOne("SELECT id FROM guardians WHERE identity_id = ? LIMIT 1", [$identityId]);
|
||||||
]
|
if ($guardian) {
|
||||||
]);
|
Database::insert("INSERT IGNORE INTO guardian_students (guardian_id, student_id) VALUES (?, ?)", [$guardian['id'], $sId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->generateSessionAndRespond(
|
||||||
|
$sId, $sUuid, 'student', 'browser_default', $decoded->phone, $fullName, $response, 'تم استكمال التسجيل بنجاح'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Legacy / Fallback for already logged-in students updating profile
|
||||||
|
$studentId = (int)$request->user_id;
|
||||||
|
if (!$studentId) {
|
||||||
|
$response->status(401)->json(['status' => 'error', 'message' => 'غير مصرح']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Database::query(
|
||||||
|
"UPDATE students SET full_name = ?, grade_level = ?, stream = ?, national_id = IF(? != '', ?, national_id), updated_at = NOW() WHERE id = ?",
|
||||||
|
[$fullName, $gradeLevel, $stream, $nationalId, $nationalId, $studentId]
|
||||||
|
);
|
||||||
|
$student = Database::selectOne("SELECT * FROM students WHERE id = ? LIMIT 1", [$studentId]);
|
||||||
|
|
||||||
|
$response->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'تم استكمال ملف الطالب بنجاح! مرحباً بك في منصة صَقِل',
|
||||||
|
'user' => [
|
||||||
|
'id' => $student['id'],
|
||||||
|
'uuid' => $student['uuid'],
|
||||||
|
'full_name' => $student['full_name'],
|
||||||
|
'grade_level' => $student['grade_level'],
|
||||||
|
'stream' => $student['stream'],
|
||||||
|
'role' => 'student'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -590,6 +590,24 @@ class StudentPortal
|
|||||||
<button type="button" onclick="switchToPhoneStep()" style="width: 100%; background: transparent; border: none; color: var(--text-muted); font-size: 12px; cursor: pointer; font-weight: 600;">← تغيير رقم الهاتف</button>
|
<button type="button" onclick="switchToPhoneStep()" style="width: 100%; background: transparent; border: none; color: var(--text-muted); font-size: 12px; cursor: pointer; font-weight: 600;">← تغيير رقم الهاتف</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2.5: National ID (Identity verification for multi-student) -->
|
||||||
|
<div id="step_national_id_container" style="display: none;">
|
||||||
|
<div style="text-align: center; margin-bottom: 24px;">
|
||||||
|
<span style="font-size: 32px;">🪪</span>
|
||||||
|
<h3 style="font-size: 16px; font-weight: 800; color: #FFF; margin-top: 6px;">الرقم الوطني للطالب</h3>
|
||||||
|
<p style="font-size: 12px; color: var(--text-muted); line-height: 1.5; margin-top: 8px;">
|
||||||
|
حفاظاً على استقلالية ملفك الدراسي وتمييزه، يرجى إدخال رقمك الوطني للوصول إلى لوحة التحكم الخاصة بك.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom: 24px;">
|
||||||
|
<input type="text" id="student_national_id_login" placeholder="أدخل الرقم الوطني المكون من 10 خانات" class="input-text" style="font-size: 16px; text-align: center; letter-spacing: 2px;" onkeypress="if(event.key==='Enter') submitNationalId()">
|
||||||
|
</div>
|
||||||
|
<button type="button" id="btn_submit_national_id" onclick="submitNationalId()" class="btn-primary" style="margin-bottom: 12px;">
|
||||||
|
<span>دخول للملف الدراسي 🚀</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="switchToPhoneStep()" style="width: 100%; background: transparent; border: none; color: var(--text-muted); font-size: 12px; cursor: pointer; font-weight: 600;">← عودة لرقم الهاتف</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Step 3: Student Onboarding Form (For New Students) -->
|
<!-- Step 3: Student Onboarding Form (For New Students) -->
|
||||||
<div id="step_onboarding_container" style="display: none;">
|
<div id="step_onboarding_container" style="display: none;">
|
||||||
<div style="text-align: center; margin-bottom: 16px;">
|
<div style="text-align: center; margin-bottom: 16px;">
|
||||||
@@ -1981,7 +1999,7 @@ class StudentPortal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyStudentOtp() {
|
async function verifyStudentOtp() {
|
||||||
const phone = document.getElementById('student_phone').value.trim();
|
const phone = document.getElementById('student_phone').value.trim();
|
||||||
const otp = document.getElementById('student_otp').value.trim();
|
const otp = document.getElementById('student_otp').value.trim();
|
||||||
if (!otp || otp.length < 4) {
|
if (!otp || otp.length < 4) {
|
||||||
@@ -1996,24 +2014,61 @@ class StudentPortal
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok && data.status === 'success') {
|
if (res.ok && data.status === 'success') {
|
||||||
const token = data.data.token;
|
if (data.data.requires_national_id) {
|
||||||
localStorage.setItem('saqel_student_jwt', token);
|
localStorage.setItem('saqel_student_identity_token', data.data.identity_token);
|
||||||
|
document.getElementById('step_otp_container').style.display = 'none';
|
||||||
// Check if student needs onboarding
|
document.getElementById('step_national_id_container').style.display = 'block';
|
||||||
const isNew = data.data.user.is_new || data.data.user.name === 'طالب جديد' || data.data.user.name === 'الطالب المتميز';
|
document.getElementById('student_national_id_login').focus();
|
||||||
if (isNew) {
|
|
||||||
switchToStudentOnboarding();
|
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem('saqel_student_user', JSON.stringify(data.data.user));
|
const token = data.data.token;
|
||||||
|
localStorage.setItem('saqel_student_jwt', token);
|
||||||
renderDashboard(data.data.user);
|
renderDashboard(data.data.user);
|
||||||
initWebSocket(token);
|
|
||||||
showLuxuryToast('أهلاً بك يا بطل! 🚀', 'تم تسجيل الدخول بنجاح.');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showError(data.message || 'رمز التحقق غير صحيح أو منتهي الصلاحية');
|
showError(data.message || 'رمز التحقق غير صحيح أو منتهي الصلاحية');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
showError('تعذر التحقق من الرمز');
|
showError('حدث خطأ في الاتصال بالسيرفر.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitNationalId() {
|
||||||
|
const nationalId = document.getElementById('student_national_id_login').value.trim();
|
||||||
|
const identityToken = localStorage.getItem('saqel_student_identity_token');
|
||||||
|
|
||||||
|
if (!nationalId) {
|
||||||
|
showError('يرجى إدخال الرقم الوطني الخاص بك');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/auth/student/login-national-id', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ national_id: nationalId, identity_token: identityToken })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && data.status === 'success') {
|
||||||
|
if (data.data && data.data.requires_onboarding) {
|
||||||
|
document.getElementById('step_national_id_container').style.display = 'none';
|
||||||
|
document.getElementById('step_onboarding_container').style.display = 'block';
|
||||||
|
document.getElementById('onboard_student_national_id').value = nationalId;
|
||||||
|
document.getElementById('onboard_student_national_id').disabled = true;
|
||||||
|
} else {
|
||||||
|
const token = data.data.token;
|
||||||
|
localStorage.setItem('saqel_student_jwt', token);
|
||||||
|
localStorage.setItem('saqel_student_user', JSON.stringify(data.data.user));
|
||||||
|
localStorage.removeItem('saqel_student_identity_token');
|
||||||
|
renderDashboard(data.data.user);
|
||||||
|
initWebSocket(token);
|
||||||
|
showLuxuryToast('أهلاً بك يا بطل! 🚀', 'تم تسجيل الدخول لملفك الخاص بنجاح.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError(data.message || 'خطأ في التحقق من الرقم الوطني');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showError('حدث خطأ في الاتصال بالخادم.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2026,7 +2081,8 @@ class StudentPortal
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function completeStudentOnboarding() {
|
async function completeStudentOnboarding() {
|
||||||
const token = localStorage.getItem('saqel_student_jwt');
|
const token = localStorage.getItem('saqel_student_jwt') || '';
|
||||||
|
const identityToken = localStorage.getItem('saqel_student_identity_token') || '';
|
||||||
const fullName = document.getElementById('onboard_student_name').value.trim();
|
const fullName = document.getElementById('onboard_student_name').value.trim();
|
||||||
const grade = document.getElementById('onboard_student_grade').value;
|
const grade = document.getElementById('onboard_student_grade').value;
|
||||||
const stream = document.getElementById('onboard_student_stream').value;
|
const stream = document.getElementById('onboard_student_stream').value;
|
||||||
@@ -2046,9 +2102,10 @@ class StudentPortal
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': 'Bearer ' + token
|
...(token ? {'Authorization': 'Bearer ' + token} : {})
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
identity_token: identityToken,
|
||||||
full_name: fullName,
|
full_name: fullName,
|
||||||
grade_level: grade,
|
grade_level: grade,
|
||||||
stream: stream,
|
stream: stream,
|
||||||
@@ -2057,10 +2114,16 @@ class StudentPortal
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok && data.status === 'success') {
|
if (res.ok && data.status === 'success') {
|
||||||
const u = data.user;
|
if (data.data && data.data.token) {
|
||||||
|
localStorage.setItem('saqel_student_jwt', data.data.token);
|
||||||
|
initWebSocket(data.data.token);
|
||||||
|
} else if (token) {
|
||||||
|
initWebSocket(token);
|
||||||
|
}
|
||||||
|
localStorage.removeItem('saqel_student_identity_token');
|
||||||
|
const u = data.user || (data.data ? data.data.user : null);
|
||||||
localStorage.setItem('saqel_student_user', JSON.stringify(u));
|
localStorage.setItem('saqel_student_user', JSON.stringify(u));
|
||||||
renderDashboard(u);
|
renderDashboard(u);
|
||||||
initWebSocket(token);
|
|
||||||
showLuxuryToast('تم استكمال الحساب بنجاح! 🚀', `مرحباً بك يا ${fullName}`);
|
showLuxuryToast('تم استكمال الحساب بنجاح! 🚀', `مرحباً بك يا ${fullName}`);
|
||||||
} else {
|
} else {
|
||||||
showError(data.message || 'فشل حفظ الملف الشخصي');
|
showError(data.message || 'فشل حفظ الملف الشخصي');
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ $router->post('/api/auth/otp/request', [\App\Controllers\AuthController::class,
|
|||||||
$router->post('/api/auth/otp/verify', [\App\Controllers\AuthController::class, 'verifyOtp'], [\App\Middlewares\RateLimitMiddleware::class]);
|
$router->post('/api/auth/otp/verify', [\App\Controllers\AuthController::class, 'verifyOtp'], [\App\Middlewares\RateLimitMiddleware::class]);
|
||||||
$router->post('/api/auth/logout', [\App\Controllers\AuthController::class, 'logout'], [\App\Middlewares\AuthMiddleware::class]);
|
$router->post('/api/auth/logout', [\App\Controllers\AuthController::class, 'logout'], [\App\Middlewares\AuthMiddleware::class]);
|
||||||
$router->get('/api/auth/me', [\App\Controllers\AuthController::class, 'me'], [\App\Middlewares\AuthMiddleware::class]);
|
$router->get('/api/auth/me', [\App\Controllers\AuthController::class, 'me'], [\App\Middlewares\AuthMiddleware::class]);
|
||||||
|
$router->post('/api/auth/student/login-national-id', [\App\Controllers\AuthController::class, 'verifyNationalId'], [\App\Middlewares\RateLimitMiddleware::class]);
|
||||||
$router->get('/api/student/profile/status', [\App\Controllers\AuthController::class, 'studentProfileStatus'], [\App\Middlewares\AuthMiddleware::class]);
|
$router->get('/api/student/profile/status', [\App\Controllers\AuthController::class, 'studentProfileStatus'], [\App\Middlewares\AuthMiddleware::class]);
|
||||||
$router->post('/api/student/profile/setup', [\App\Controllers\AuthController::class, 'studentProfileSetup'], [\App\Middlewares\AuthMiddleware::class]);
|
$router->post('/api/student/profile/setup', [\App\Controllers\AuthController::class, 'studentProfileSetup'], [\App\Middlewares\AuthMiddleware::class]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user