Snai3i-MarketPlace / backend / src / services / auth / auth.service.ts
auth.service.ts
Raw
import authLogs, { IAuthLogs, authLogger } from './auth.logs';
import { formatString } from '../../utils/Strings';
import { generateToken } from '../../utils/Jwt';
import { HttpCodes } from '../../config/Errors';
import { ErrorResponseC, SuccessResponseC } from '../services.response';
import { Response } from 'express';
import { db } from '../../settings';
import bcrypt from 'bcrypt';
import { ResultSetHeader, RowDataPacket } from 'mysql2';
import { Optimize } from '../../utils/Function';

export class AuthServices {
  /**
   * @description  Login a user
   * @param email  - String
   * @param password - String
   * @returns  ResponseT
   */
  static executeLogin = async (
    res: Response,
    email: string,
    password: string
  ): Promise<ResponseT> => {
    try {
      const sqlquery = 'SELECT * FROM users WHERE email = ?';
      const [[user]] = await db.query<UserI[]>(sqlquery, [email]);
      if (user) {
        const isPasswordMatch = bcrypt.compareSync(password, user.password);
        if (isPasswordMatch) {
          //   query additional info base on role
          const additionalInfo = await this.getAdditionalInfo(
            user.role,
            user.user_id
          );
          if (additionalInfo.isErr) {
            const msg = formatString(authLogs.LOGIN_ERROR_GENERIC.message, {
              error: (additionalInfo.err as Error)?.message || '',
              email,
            });
            authLogger.error(msg, additionalInfo.err as Error);
            return new ErrorResponseC(
              authLogs.LOGIN_ERROR_GENERIC.type,
              HttpCodes.InternalServerError.code,
              msg
            );
          }

          generateToken(res, {
            user_id: user.user_id.toString(),
            role: user.role,
          });

          const resp: ICode<IAuthLogs> = authLogs.LOGIN_SUCCESS;
          const msg = formatString(resp.message, {
            email: user.email,
            lastName: user.lastName,
            firstName: user.firstName,
          });
          authLogger.info(msg, { type: resp.type });

          return new SuccessResponseC(
            resp.type,
            { ...Optimize(user), ...additionalInfo.data },
            msg,
            HttpCodes.Accepted.code
          );
        }
        const msg = formatString(
          authLogs.LOGIN_ERROR_INCORRECT_PASSWORD_FOUND.message,
          { email }
        );
        authLogger.error(msg);
        return new ErrorResponseC(
          authLogs.LOGIN_ERROR_INCORRECT_PASSWORD_FOUND.type,
          HttpCodes.Unauthorized.code,
          msg
        );
      }
      const msg = formatString(authLogs.LOGIN_ERROR_EMAIL_NOT_FOUND.message, {
        email,
      });
      authLogger.error(msg);
      return new ErrorResponseC(
        authLogs.LOGIN_ERROR_EMAIL_NOT_FOUND.type,
        HttpCodes.NotFound.code,
        msg
      );
    } catch (err) {
      const msg = formatString(authLogs.LOGIN_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
        email,
      });
      authLogger.error(msg, err as Error);
      return new ErrorResponseC(
        authLogs.LOGIN_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Register a user
   * @returns {ResponseT}
   */
  static executeRegister = async (
    res: Response,
    email: string,
    password: string,
    firstName: string,
    lastName: string,
    role: string,
    phone: string,
    additionalInfo: any
  ): Promise<ResponseT> => {
    try {
      const sqlquery = 'SELECT * FROM users WHERE email = ?';
      const [[userExist]] = await db.query<UserI[]>(sqlquery, [email]);
      if (userExist) {
        const msg = formatString(authLogs.REGISTER_ERROR_EMAIL_EXIST.message, {
          email,
        });
        authLogger.error(msg);
        return new ErrorResponseC(
          authLogs.REGISTER_ERROR_EMAIL_EXIST.type,
          HttpCodes.Conflict.code,
          msg
        );
      }
      const hashedPassword = bcrypt.hashSync(password, 10);
      const sqlInsertQuery =
        'INSERT INTO users (email, password, firstName, lastName, role, phone) VALUES (?, ?, ?, ?, ?, ?)';

      const result: any = await db.query<ResultSetHeader[]>(sqlInsertQuery, [
        email,
        hashedPassword,
        firstName,
        lastName,
        role,
        phone,
      ]);

      const userId = result[0].insertId;

      const user = {
        user_id: userId,
        email,
        firstName,
        lastName,
        role,
        isActive: 1,
        phone,
      };
      // const sqlquery2 = 'SELECT * FROM users WHERE user_id = ?';
      // const [[user]] = await db.query<RowDataPacket[]>(sqlquery2, [userId]);

      // insert additional info on table base on role
      if (role !== 'admin' && role !== 'super_admin' && role !== 'inst_admin') {
        const insertAdditionalInfo = await this.insertAdditionalInfo(
          role,
          userId.toString(),
          firstName,
          lastName,
          additionalInfo
        );
        if (insertAdditionalInfo.isErr) {
          const sqlDeleteQuery = 'DELETE FROM users WHERE user_id = ?';
          await db.query<RowDataPacket[]>(sqlDeleteQuery, [userId]);
          const msg = formatString(authLogs.REGISTER_ERROR_GENERIC.message, {
            error: (insertAdditionalInfo.err as Error)?.message || '',
            email,
          });
          authLogger.error(msg, insertAdditionalInfo.err as Error);
          return new ErrorResponseC(
            authLogs.REGISTER_ERROR_GENERIC.type,
            HttpCodes.InternalServerError.code,
            msg
          );
        }
        additionalInfo = {
          [`${role}_id`]: insertAdditionalInfo.insertId,
          ...additionalInfo,
        };
      }

      // generateToken(res, {
      //   user_id: userId.toString(),
      //   role,
      // });

      const resp: ICode<IAuthLogs> = authLogs.REGISTER_SUCCESS;
      const msg = formatString(resp.message, {
        email,
        lastName,
        firstName,
      });
      authLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        {
          ...Optimize(user),
          ...additionalInfo,
        },
        msg,
        HttpCodes.Created.code
      );
    } catch (err) {
      const msg = formatString(authLogs.REGISTER_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
        email,
      });
      authLogger.error(msg, err as Error);
      return new ErrorResponseC(
        authLogs.REGISTER_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static executeAuthBack = async (user_id: number): Promise<ResponseT> => {
    try {
      const sqlquery = 'SELECT * FROM users WHERE user_id = ?';
      const [[user]] = await db.query<UserI[]>(sqlquery, [user_id]);
      if (!user) {
        const msg = formatString(authLogs.USER_NOT_FOUND.message, {
          userId: user_id,
        });
        authLogger.error(msg);
        return new ErrorResponseC(
          authLogs.USER_NOT_FOUND.type,
          HttpCodes.NotFound.code,
          msg
        );
      }

      // query additional info base on role
      const additionalInfo = await this.getAdditionalInfo(
        user.role,
        user.user_id
      );
      if (additionalInfo.isErr) {
        const msg = formatString(authLogs.AUTH_ERROR_GENERIC.message, {
          email: user.email,
          error: (additionalInfo.err as Error)?.message || '',
        });
        authLogger.error(msg, additionalInfo.err as Error);
        return new ErrorResponseC(
          authLogs.AUTH_ERROR_GENERIC.type,
          HttpCodes.InternalServerError.code,
          msg
        );
      }

      const resp: ICode<IAuthLogs> = authLogs.AUTH_BACK;
      const msg = formatString(resp.message, {
        email: user.email,
        username: user.firstName + ' ' + user.lastName,
      });
      authLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        { ...Optimize(user), ...additionalInfo.data },
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(authLogs.AUTH_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
        user_id,
      });
      authLogger.error(msg, err as Error);
      return new ErrorResponseC(
        authLogs.AUTH_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static executeLogout = async (res: Response): Promise<ResponseT> => {
    try {
      res.clearCookie('token');
      const resp: ICode<IAuthLogs> = authLogs.LOGOUT_SUCCESS;
      const msg = resp.message;
      authLogger.info(msg, { type: resp.type });
      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(authLogs.LOGOUT_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
      });
      authLogger.error(msg, err as Error);
      return new ErrorResponseC(
        authLogs.LOGOUT_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static executeResetPassword = async (
    userId: number,
    newPassword: string
  ): Promise<ResponseT> => {
    try {
      const hashedPassword = bcrypt.hashSync(newPassword, 10);
      const sqlquery = 'UPDATE users SET password = ? WHERE user_id = ?';
      await db.query<RowDataPacket[]>(sqlquery, [hashedPassword, userId]);

      const resp: ICode<IAuthLogs> = authLogs.RESET_PASSWORD_SUCCESS;
      const msg = formatString(resp.message, {
        user: userId,
      });
      authLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(authLogs.RESET_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
        email: userId,
      });
      authLogger.error(msg, err as Error);
      return new ErrorResponseC(
        authLogs.RESET_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static executeResetTeacherPassword = async (
    teacherId: number,
    newPassword: string
  ): Promise<ResponseT> => {
    try {
      const sqlTeacherQuery = 'SELECT * FROM teachers WHERE teacher_id = ?';
      const [[teacher]] = await db.query<Teacher[]>(sqlTeacherQuery, [
        teacherId,
      ]);
      if (!teacher) {
        const msg = formatString(authLogs.USER_NOT_FOUND.message, {
          userId: teacherId,
        });
        authLogger.error(msg);
        return new ErrorResponseC(
          authLogs.USER_NOT_FOUND.type,
          HttpCodes.NotFound.code,
          msg
        );
      }

      const hashedPassword = bcrypt.hashSync(newPassword, 10);
      const sqlquery = 'UPDATE users SET password = ? WHERE user_id = ?';
      await db.query<RowDataPacket[]>(sqlquery, [
        hashedPassword,
        teacher.user_id,
      ]);

      const resp: ICode<IAuthLogs> = authLogs.RESET_PASSWORD_SUCCESS;
      const msg = formatString(resp.message, {
        user: teacherId,
      });
      authLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(authLogs.RESET_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
        email: teacherId,
      });
      authLogger.error(msg, err as Error);
      return new ErrorResponseC(
        authLogs.RESET_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static getAdditionalInfo = async (
    role: string,
    user_id: number
  ): Promise<{
    data: any;
    isErr: boolean;
    err: any;
  }> => {
    if (role !== 'admin' && role !== 'super_admin' && role !== 'inst_admin') {
      const tableName =
        role === 'teacher'
          ? 'teachers'
          : role === 'school'
          ? 'schools'
          : 'inst_designers';
      const sqlquery = `SELECT * FROM ${tableName} WHERE user_id = ?`;

      try {
        const [[additionalInfo]]: any = await db.query<RowDataPacket[]>(
          sqlquery,
          user_id
        );

        return {
          data: additionalInfo,
          isErr: false,
          err: null,
        };
      } catch (err) {
        return {
          data: null,
          isErr: true,
          err,
        };
      }
    }
    return {
      isErr: false,
      err: null,
      data: null,
    };
  };

  static insertAdditionalInfo = async (
    role: string,
    user_id: number,
    firstName: string,
    lastName: string,
    additionalInfo: any
  ): Promise<{
    insertId: number | null;
    isErr: boolean;
    err: any;
  }> => {
    const tableName =
      role === 'teacher'
        ? 'teachers'
        : role === 'school'
        ? 'schools'
        : 'inst_designers';

    const additionalInfoKeys = Object.keys(additionalInfo);
    const additionalInfoValues = Object.values(additionalInfo);

    additionalInfoKeys.push('user_id');
    additionalInfoValues.push(user_id);

    if (role === 'inst_designer') {
      additionalInfoKeys.push('firstName');
      additionalInfoValues.push(firstName);
      additionalInfoKeys.push('lastName');
      additionalInfoValues.push(lastName);
    }

    const additionalInfoColumns = additionalInfoKeys.join(',');

    try {
      const sqlquery = `INSERT INTO ${tableName} (${additionalInfoColumns}) VALUES (?)`;

      const [result]: any = await db.query<RowDataPacket[]>(sqlquery, [
        additionalInfoValues,
      ]);

      return { insertId: result.insertId, isErr: false, err: null };
    } catch (err) {
      return { insertId: null, isErr: true, err };
    }
  };
}