getBody(); $validator = new Validator(); $isValid = $validator->validate($body, [ 'full_name' => 'required', 'phone_number' => 'required', 'password' => 'required|min:8', 'role' => 'required' ]); if (!$isValid) { $response->status(400)->json([ 'status' => 'error', 'message' => 'Validation failed', 'errors' => $validator->getErrors() ]); return; } $phone = $body['phone_number']; $role = $body['role']; if (!in_array($role, ['student', 'teacher', 'guardian'])) { $response->status(400)->json([ 'status' => 'error', 'message' => 'Invalid role specified' ]); return; } $phoneHash = Security::blindIndex($phone); $existing = Database::selectOne("SELECT id FROM users WHERE phone_hash = ? LIMIT 1", [$phoneHash]); if ($existing) { $response->status(409)->json([ 'status' => 'error', 'message' => 'Phone number is already registered' ]); return; } $passwordHash = Security::hashPassword($body['password']); // Generate UUID $uuid = 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) ); $userId = Database::insert( "INSERT INTO users (uuid, full_name, phone_number, phone_hash, password_hash, role, status) VALUES (?, ?, ?, ?, ?, ?, 'active')", [$uuid, $body['full_name'], $phone, $phoneHash, $passwordHash, $role] ); $this->generateSessionAndRespond($userId, $uuid, $role, $response, "User registered successfully"); } /** * Login using Phone Number and Password */ public function login(Request $request, Response $response): void { $body = $request->getBody(); $validator = new Validator(); if (!$validator->validate($body, [ 'phone_number' => 'required', 'password' => 'required' ])) { $response->status(400)->json([ 'status' => 'error', 'message' => 'Validation failed', 'errors' => $validator->getErrors() ]); return; } $phoneHash = Security::blindIndex($body['phone_number']); $user = Database::selectOne("SELECT * FROM users WHERE phone_hash = ? LIMIT 1", [$phoneHash]); if (!$user || !Security::verifyPassword($body['password'], $user['password_hash'])) { $response->status(401)->json([ 'status' => 'error', 'message' => 'Invalid phone number or password' ]); return; } if ($user['status'] === 'suspended') { $response->status(403)->json([ 'status' => 'error', 'message' => 'Account is suspended' ]); return; } $this->generateSessionAndRespond($user['id'], $user['uuid'], $user['role'], $response, "Login successful"); } /** * Request OTP for phone verification */ public function requestOtp(Request $request, Response $response): void { $body = $request->getBody(); if (empty($body['phone_number'])) { $response->status(400)->json(['status' => 'error', 'message' => 'Phone number is required']); return; } $phone = $body['phone_number']; $otp = (string)random_int(100000, 999999); // Save OTP to Redis for 5 minutes $redis = RedisClient::getInstance(); $redis->setex('otp:' . $phone, 300, $otp); // TODO: Integrate SMS gateway here to actually send the OTP via SMS $response->json([ 'status' => 'success', 'message' => 'OTP sent successfully (Simulated: ' . $otp . ')' ]); } /** * Common method to generate JWT and save session to Redis */ private function generateSessionAndRespond(int $userId, string $uuid, string $role, Response $response, string $msg): void { $payload = [ 'user_id' => $userId, 'uuid' => $uuid, 'role' => $role ]; $token = Security::generateJWT($payload); // Store session in Redis (Active for 30 days) try { $redis = RedisClient::getInstance(); $redis->setex("session:{$userId}:{$token}", 30 * 86400, "active"); } catch (\Exception $e) { error_log("Failed to save session to Redis: " . $e->getMessage()); } $response->status(200)->json([ 'status' => 'success', 'message' => $msg, 'data' => [ 'token' => $token, 'user' => [ 'uuid' => $uuid, 'role' => $role ] ] ]); } /** * Get Current User Data */ public function me(Request $request, Response $response): void { $userId = $request->user_id; $user = Database::selectOne( "SELECT uuid, full_name, role, status, created_at FROM users WHERE id = ? LIMIT 1", [$userId] ); if (!$user) { $response->status(404)->json([ 'status' => 'error', 'message' => 'User not found' ]); return; } $response->json([ 'status' => 'success', 'data' => $user ]); } }