Viewing File: /home/assersoft/public_html/doctor-assistant/app/Controllers/UsersController.php

<?php

namespace App\Controllers;

require_once __DIR__ . "/../Helpers/jwt_helper.php";

use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
use App\Models\ClinicsModel;

helper('cookie');

class UsersController extends ResourceController
{

    protected $modelName = "App\Models\UsersModel";
    protected $format = "json";

    /**
     * Return an array of resource objects, themselves in array format.
     *
     * @return ResponseInterface
     */
    public function index()
    {
        $rules = [
            'clinic_id' => 'required|numeric',
        ];

        if(!$this->validate($rules)) {
            return $this->fail($this->validator->getErrors());
        }

        $data = $this->model->where('clinic_id', $this->request->getVar('clinic_id'))->findAll();

        foreach($data as &$user) {
            unset($user['password']);
            unset($user['refresh_token']);
            unset($user['activation_token']);
            unset($user['reset_token']);
        }

        return $this->respond($data);
    }

    /**
     * Return the properties of a resource object.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function show($id = null)
    {
        $data = $this->model->find($id);
        if($data == null){
            return $this->failNotFound("User not found");
        }

        unset($data['password']);
        unset($data['refresh_token']);
        unset($data['activation_token']);
        unset($data['reset_token']);

        return $this->respond($data);
    }

    /**
     * Create a new resource object, from "posted" parameters.
     *
     * @return ResponseInterface
     */
    public function create()
    {
        $rules = [
            'first_name' => 'required|min_length[3]|max_length[50]',
            'last_name' => 'permit_empty|min_length[3]|max_length[50]',
            'email' => 'required|valid_email',
            'phone' => 'required',
            'password' => 'required|min_length[8]',
            'user_type' => 'required|in_list[provider, head_doctor, doctor,receptionist]',
            'consultation_hours' => 'permit_empty|json',
            'clinic_id' => 'permit_empty|numberic',
            'clinic_name' => 'permit_empty|min_length[3]|max_length[50]',
            'refresh_token' => 'permit_empty',
        ];

        if(!$this->validate($rules)) {
            return $this->fail($this->validator->getErrors());
        }

        $data = $this->request->getVar();
        $data->subscription_status = false;
        $data->subscription_end_date = null;
        $data->is_verified = false;

        $user = $this->request->user ?? null;

        if ($data->user_type == 'provider' && $user->user_type && $user->user_type !== 'provider') {
            return $this->fail('Only providers can create more providers');
        }

        if($data->user_type !== 'head_doctor' && $data->user_type !== 'provider' && !property_exists($data, 'clinic_id')) {
            return $this->fail('Clinic ID is required for non-head doctors');
        }

        if($data->user_type != 'head_doctor' && $user->user_type && ($user->user_type != 'head_doctor' && $user->user_type != 'provider')) {
            return $this->fail('Only head doctors can create doctors and receptionists');
        }

        if($data->user_type == 'head_doctor') {
            $clinicsModel = new ClinicsModel();
            $data->subscription_status = true;
            $data->subscription_end_date = date('Y-m-d', strtotime('+7 days'));
            if (!$clinicsModel->insert($data)) {
                return $this->fail($clinicsModel->errors());
            }

            $data->clinic_id = $clinicsModel->getInsertID();
        }

        $data->password = password_hash($data->password, PASSWORD_DEFAULT);

        if(!$this->model->insert($data)) {
//        if(!$this->model->save($data)) {
            return $this->fail($this->model->errors());
        }

        $activationToken = generateToken(['user_id' => $this->model->insertID()], getenv('jwt.activation_token_expiration'));
        $this->model->update($this->model->insertID(), ['activation_token' => $activationToken]);

        // sending email
        $message = "Please activate your account: " . anchor(base_url("user/activate/" . $activationToken), "Activate Now");

        $email = \Config\Services::email();
        $email->setTo($data->email);
        $email->setSubject('Activate your account');
        $email->setMessage($message);
//        $email->send();
        if(!$email->send()) {
            // return $this->fail($email->printDebugger());
            return $this->fail(['error' => 'Failed to send activation email', 'debug' => $email->printDebugger()]);
        }

        return $this->respondCreated([ 'user_id' => $this->model->getInsertID() ]);
    }

    public function activate($token = null) {
        $decodedToken = decodeJWT($token);
        if(is_object($decodedToken) && property_exists($decodedToken, 'error')) {
            return view('activation_error', ['error' => 'Invalid token']);
        }

        $user = $this->model->find($decodedToken->user_id);
        if ($user == null) {
            return view('activation_error');
        }

        if($token != $user['activation_token']) {
            return view('activation_error', ['error' => 'Invalid or expired token']);
        }

        if ($user['is_verified']) {
            return view('activation_error', ['message' => 'Your account is already verified']);
        }

        $this->model->update($decodedToken->user_id, ['is_verified' => true, 'activation_token' => null]);

        return view('activation_success');
    }

    /**
     * Add or update a model resource, from "posted" properties.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function update($id = null)
    {
        $rules = [
            'first_name' => 'permit_empty|min_length[3]|max_length[50]',
            'last_name' => 'permit_empty|min_length[3]|max_length[50]',
            'email' => 'permit_empty|valid_email',
            'phone' => 'permit_empty',
            'password' => 'permit_empty|min_length[8]',
            'user_type' => 'permit_empty|in_list[head_doctor, doctor,receptionist]',
            'consultation_hours' => 'permit_empty|json',
            'clinic_id' => 'permit_empty|numeric',
            'clinic_name' => 'permit_empty|min_length[3]|max_length[50]',
            'refresh_token' => 'permit_empty',
        ];

        if(!$this->validate($rules)) {
            return $this->fail($this->validator->getErrors());
        }

        $user = $this->model->find($id);
        if($user == null) {
            return $this->failNotFound("User not found");
        }

        $data = $this->request->getVar();
        if(property_exists($data, 'email') && $data->email !== $user->email) {
            $data->is_verified = false;
            $activationToken = generateToken(['user_id' => $id], getenv('jwt.activation_token_expiration'));
            $data->verification_token = $activationToken;

            // sending email
            $message = "Please activate your account: " . anchor(base_url("user/activate/" . $activationToken), "Activate Now");

            $email = \Config\Services::email();
            $email->setTo($user['email']);
            $email->setSubject('Activate your account');
            $email->setMessage($message);
            // $email->send();
            if(!$email->send()) {
                // return $this->fail($email->printDebugger());
                return $this->fail(['error' => 'Failed to send activation email', 'debug' => $email->printDebugger()]);
            }
        }

        if(property_exists($data, 'password')) {
            $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
        }

        $this->model->update($id, $data);
        if ($this->model->affectedRows() == 0) {
            return $this->fail($this->model->errors());
        }

        return $this->respondUpdated(['user_id' => $id]);
    }

    /**
     * Delete the designated resource object from the model.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function delete($id = null)
    {
        $user = $this->model->find($id);
        if($user == null) {
            return $this->failNotFound("User not found");
        }

        $this->model->delete($id);
        if($this->model->affectedRows() == 0) {
            return $this->fail($this->model->errors());
        }
        return $this->respondDeleted(['user_id' => $id]);
    }

    public function login() {
        $rules = [
            'user_id' => 'required|numeric',
            'password' => 'required',
        ];

        if(!$this->validate($rules)) {
            return $this->fail($this->validator->getErrors());
        }

        $data = $this->request->getVar();
        $user = $this->model->where('user_id', $data->user_id)->first();
        if($user == null) {
            return $this->failNotFound("User not found");
        }

        if(!password_verify($data->password, $user['password'])) {
            return $this->fail("Incorrect password");
        }

        if(!$user['is_verified']) {
            return $this->fail("Email not verified");
        }

        $clinicsModel = new ClinicsModel();
        $clinic = $clinicsModel->find((int)$user['clinic_id']);
        if($clinic == null) {
            return $this->failNotFound("Clinic not found");
        }

        $user['clinic'] = $clinic;

        $accessToken = generateToken(
            ['user_id' => $user['user_id'], 'user_type' => $user['user_type'], 'clinic_id' => $user['clinic_id']],
            getenv('jwt.access_token_expiration')
        );
        $refreshToken = generateToken(
            ['user_id' => $user['user_id']],
            getenv('jwt.refresh_token_expiration')
        );

        $this->model->update($user['user_id'], [
            'refresh_token' => $refreshToken
        ]);

        set_cookie('access_token', $accessToken, getenv('jwt.access_token_expiration'));
        set_cookie('refresh_token', $refreshToken, getenv('jwt.refresh_token_expiration'));

        unset($user['password']);
        unset($user['refresh_token']);
        unset($user['activation_token']);
        unset($user['reset_token']);

        return $this->respond([
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'user' => $user
        ]);
    }

    public function refresh() {
        $refreshToken = get_cookie('refresh_token');
        if(empty($refreshToken)) {
            return $this->fail("Refresh token not found");
        }

        $payload = decodeJWT($refreshToken);
        if(is_object($payload) && property_exists($payload, 'refresh_token')) {
            return $this->fail("Invalid refresh token");
        }

        $user = $this->model->find($payload->user_id);
        if($user == null) {
            return $this->failNotFound("User not found");
        }

        if ($user['refresh_token'] !== $refreshToken) {
            return $this->failUnauthorized('Refresh token mismatch.');
        }

        $accessToken = generateToken(
            ['user_id' => $user['user_id'], 'user_type' => $user['user_type']],
            getenv('jwt.access_token_expiration')
        );

        set_cookie('access_token', $accessToken, getenv('jwt.access_token_expiration'));

        unset($user['password']);
        unset($user['refresh_token']);
        unset($user['activation_token']);
        unset($user['reset_token']);

        return $this->respond([
            'access_token' => $accessToken,
            'user' => $user
        ]);
    }

    public function logout() {
        $refreshToken = get_cookie('refresh_token');
        if(empty($refreshToken)) {
            return $this->fail("Refresh token not found");
        }

        $payload = decodeJWT($refreshToken);
        if(is_object($payload) && property_exists($payload, 'error')) {
            return $this->fail("Invalid refresh token");
        }

        $user = $this->model->find($payload->user_id);
        if (!$user) {
            return $this->failNotFound('User not found.');
        }

        if ($user['refresh_token'] !== $refreshToken) {
            return $this->failUnauthorized('Refresh token mismatch.');
        }

        $this->model->update($user['user_id'], [
            'refresh_token' => null
        ]);

        set_cookie('access_token', '', -1);
        set_cookie('refresh_token', '', -1);

        return $this->respond(['message' => "Logged out successfully"]);
    }

    public function resetPassword() {
        $rules = [
          'user_id' => 'required|numeric',
        ];

        if(!$this->validate($rules)) {
            return $this->fail($this->validator->getErrors());
        }

        $user = $this->model->where('user_id', $this->request->getVar('user_id'))->first();
        if($user == null) {
            return $this->failNotFound("User not found");
        }

        $resetToken = generateToken([
            'user_id' => $user['user_id'],
            'clinic_id' => $user['clinic_id']
        ], getenv('jwt.reset_password_token_expiration'));

        $this->model->update($user['user_id'], [
            'reset_token' => $resetToken
        ]);
        
        if (!isset($user['email']) || empty($user['email'])) {
            return $this->fail(['error' => 'Invalid email address']);
        }

        $message = "Please reset your password: " . anchor(base_url("user/reset-password/" . $resetToken), "Reset Password");
        $email = \Config\Services::email();
        $email->setTo($user['email']);
        $email->setSubject('Reset Password');
        $email->setMessage($message);
        if(!$email->send()) {
            // return $this->fail($email->printDebugger());
            return $this->fail(['error' => 'Failed to send reset password email', 'debug' => $email->printDebugger()]);
        }

        return $this->respond(['Reset link has been sent to your email.']);
    }

    public function reset($token = null) {
        if (empty($token)) {
            return $this->failNotFound('Token is missing');
        }

        $decodedToken = decodeJWT($token);
        if (is_object($decodedToken) && property_exists($decodedToken, 'error')) {
            return $this->fail($decodedToken['error']);
        }

        $user = $this->model->find($decodedToken->user_id);
        if ($user == null) {
            return $this->failNotFound('User not found');
        }

        if ($user['reset_token'] !== $token) {
            return $this->failUnauthorized('Reset token mismatch.');
        }

        if ($this->request->getMethod() === 'GET') {
            return view('reset_password_form', ['token' => $token]);
        }

        $password = $this->request->getPost('password');
        $confirmPassword = $this->request->getPost('confirm_password');

        if ($password !== $confirmPassword) {
            return $this->failValidationErrors('Passwords do not match.');
        }

        if (strlen($password) < 8) {
            return $this->failValidationErrors('Password must be at least 8 characters.');
        }

        $this->model->update($user['user_id'], [
            'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
            'reset_token' => null
        ]);

        return view('reset_successful');
    }

}
Back to Directory File Manager