{
  "slug": "stateful-auth",
  "runtimes": {
    "node": {
      "frameworks": {
        "express": {
          "databases": {
            "mongodb": {
              "orms": {
                "mongoose": {
                  "dependencies": {
                    "runtime": [
                      "express",
                      "mongoose",
                      "argon2",
                      "cloudinary",
                      "cookie-parser",
                      "cors",
                      "express-rate-limit",
                      "helmet",
                      "multer",
                      "nodemailer",
                      "passport",
                      "passport-github2",
                      "passport-google-oauth20",
                      "pino",
                      "pino-pretty",
                      "zod",
                      "dotenv-flow",
                      "cross-env",
                      "source-map-support",
                      "swagger-autogen",
                      "swagger-ui-express"
                    ],
                    "dev": [
                      "@types/express",
                      "@types/cookie-parser",
                      "@types/cors",
                      "@types/morgan",
                      "@types/multer",
                      "@types/nodemailer",
                      "@types/passport",
                      "@types/passport-github2",
                      "@types/passport-google-oauth20",
                      "morgan",
                      "@types/source-map-support",
                      "@types/swagger-ui-express"
                    ]
                  },
                  "env": [
                    "PORT",
                    "NODE_ENV",
                    "LOG_LEVEL",
                    "CORS_ORIGIN",
                    "CRYPTO_SECRET",
                    "DATABASE_URL",
                    "JWT_ACCESS_SECRET",
                    "JWT_REFRESH_SECRET",
                    "SMTP_HOST",
                    "SMTP_PORT",
                    "SMTP_USER",
                    "SMTP_PASS",
                    "EMAIL_FROM",
                    "CLOUDINARY_CLOUD_NAME",
                    "CLOUDINARY_API_KEY",
                    "CLOUDINARY_API_SECRET",
                    "GITHUB_CLIENT_ID",
                    "GITHUB_CLIENT_SECRET",
                    "GITHUB_REDIRECT_URI",
                    "GOOGLE_CLIENT_ID",
                    "GOOGLE_CLIENT_SECRET",
                    "GOOGLE_REDIRECT_URI"
                  ],
                  "architectures": {
                    "mvc": {
                      "files": [
                        {
                          "type": "file",
                          "path": "swagger.config.ts",
                          "content": "import swaggerAutoGen from \"swagger-autogen\";\n\nconst doc = {\n  info: {\n    title: \"Stateful authentication\",\n    description: \"Stateful authentication API documentation\",\n    version: \"1.0.0\"\n  },\n  host: \"localhost:9000\",\n  schemes: [\"http\"]\n};\n\nconst outputFile = \"./src/docs/swagger.json\"; // Output file for the generated docs\nconst endpointsFiles = [\"./src/routes/*.ts\"]; // Endpoints files to be parsed\n\nswaggerAutoGen(outputFile, endpointsFiles, doc);\n"
                        },
                        {
                          "type": "file",
                          "path": "package.json",
                          "content": "{\n  \"name\": \"servercn-stateful-auth\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/server.js\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"cross-env NODE_ENV=development npx tsx watch src/server.ts\",\n    \"build\": \"rm -rf dist && tsc && tsc-alias\",\n    \"start\": \"cross-env NODE_ENV=production node dist/server.js\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"docs\": \"npx tsx swagger.config.ts\",\n    \"prepare\": \"husky\",\n    \"lint:check\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"format:check\": \"npx prettier . --check\",\n    \"format:fix\": \"npx prettier . --write\"\n  },\n  \"lint-staged\": {\n    \"src/**/*.ts\": [\n      \"eslint --fix\",\n      \"prettier --write\"\n    ]\n  },\n  \"devDependencies\": {},\n  \"dependencies\": {}\n}\n"
                        },
                        {
                          "type": "file",
                          "path": ".husky/pre-commit",
                          "content": "npx lint-staged"
                        },
                        {
                          "type": "file",
                          "path": "src/server.ts",
                          "content": "import app from \"./app\";\nimport { connectDB } from \"./configs/db\";\nimport env from \"./configs/env\";\nimport { logger } from \"./utils/logger\";\nimport { configureGracefulShutdown } from \"./utils/shutdown\";\n\nconst port = env.PORT || 9000;\n\nconnectDB();\n\nconst server = app.listen(port, () => {\n  logger.info(`[server]: Server is running at http://localhost:${port}`);\n  logger.info(`[server]: Environment: ${port}`);\n  logger.info(`[server]: Swagger Docs: http://localhost:${port}/api/docs`);\n});\n\nconfigureGracefulShutdown(server);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/app.ts",
                          "content": "import express, { Express, Request, Response } from \"express\";\nimport cookieParser from \"cookie-parser\";\nimport morgan from \"morgan\";\nimport { notFoundHandler } from \"./middlewares/not-found-handler\";\nimport { errorHandler } from \"./middlewares/error-handler\";\nimport env from \"./configs/env\";\nimport { configureSecurityHeaders } from \"./middlewares/security-header\";\n\nimport Routes from \"./routes/index\";\n\nimport \"./configs/passport\";\nimport { setupSwagger } from \"./configs/swagger\";\n\nconst app: Express = express();\n\n//? Apply security headers before other middlewares and routes\nconfigureSecurityHeaders(app);\n\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(cookieParser());\napp.use(morgan(env.NODE_ENV === \"development\" ? \"dev\" : \"combined\"));\n\n//? Initialize Swagger\nsetupSwagger(app);\n\n//? Routes\napp.get(\"/\", (req: Request, res: Response) => {\n  res.redirect(\"/api/v1/health\");\n});\n\napp.use(\"/api\", Routes);\n\n//? Not-found-handler (should be after routes)\napp.use(notFoundHandler);\n\n//? Global error handler (should be last)\napp.use(errorHandler);\n\nexport default app;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/validators/auth.ts",
                          "content": "import * as z from \"zod\";\nimport { OTP_TYPES } from \"../constants/auth\";\n\nexport const nameSchema = z\n  .string({ error: \"Name must be a string\" })\n  .trim()\n  .min(3, {\n    message: \"Name must be at least 3 characters long\"\n  })\n  .max(50, {\n    message: \"Name must be at most 50 characters long\"\n  });\n\nexport const passwordSchema = z\n  .string({ error: \"Password must be a string\" })\n  .trim()\n  .min(6, {\n    message: \"Password must be at least 6 characters long\"\n  })\n  .max(80, {\n    message: \"Password must be at most 80 characters long\"\n  });\n\nexport const emailSchema = z\n  .email({ message: \"Please enter a valid email address.\" })\n  .max(100, { message: \"Email must be no more than 100 characters.\" });\n\nexport const roleSchema = z\n  .enum([\"user\", \"admin\"], {\n    error: \"Role must be either applicant, recruiter, or admin\"\n  })\n  .default(\"user\");\n\nexport const SigninSchema = z.object({\n  email: emailSchema,\n  password: z.string({ error: \"Password must be a string\" }).trim().min(1, {\n    message: \"Password is required\"\n  })\n});\n\nexport const SignupSchema = z\n  .object({\n    name: nameSchema,\n    email: emailSchema,\n    password: passwordSchema,\n    confirmPassword: passwordSchema,\n    role: roleSchema\n  })\n  .refine(\n    data => {\n      return data.password === data.confirmPassword;\n    },\n    {\n      message: \"Passwords do not match\",\n      path: [\"confirmPassword\"]\n    }\n  );\n\nexport const RequestOtpSchema = z.object({\n  email: emailSchema,\n  otpType: z.enum(OTP_TYPES, { error: \"Invalid otp type\" })\n});\n\nexport const VerifyOtpSchema = z.object({\n  otpCode: z.string().min(6, \"Please enter a valid OTP\"),\n  email: emailSchema,\n  otpType: z.enum(OTP_TYPES, { error: \"Invalid otp type\" })\n});\n\nexport const ResetPasswordSchema = z.object({\n  email: emailSchema,\n  newPassword: passwordSchema\n});\n\nexport const ChangePasswordSchema = z.object({\n  oldPassword: z.string({ error: \"Password must be a string\" }).min(1, {\n    message: \"Old password is required\"\n  }),\n  newPassword: passwordSchema\n});\n\nexport const UpdateProfileSchema = z.object({\n  name: nameSchema.optional(),\n  avatar: z.string().optional()\n});\n\nexport const GoogleSigninSchema = z.object({\n  name: nameSchema,\n  email: emailSchema,\n  provider: z.enum([\"google\", \"github\"]).default(\"google\"),\n  providerId: z.string({ error: \"Provider id must be a string\" }).min(1, {\n    message: \"Provider id is required\"\n  }),\n  avatar: z.string().optional(),\n  isEmailVerified: z.boolean().default(false)\n});\n\nexport const DeleteAccountSchema = z.object({\n  userId: z.string({ error: \"User id must be a string\" }).min(1, {\n    message: \"User id is required\"\n  }),\n  type: z\n    .enum([\"soft\", \"hard\"], { error: \"Type must be either soft or hard\" })\n    .default(\"soft\")\n});\n\nexport type SignupUserType = z.infer<typeof SignupSchema>;\nexport type SigninUserType = z.infer<typeof SigninSchema>;\nexport type RequestOtpType = z.infer<typeof RequestOtpSchema>;\nexport type VerifyOtpType = z.infer<typeof VerifyOtpSchema>;\nexport type ResetPasswordType = z.infer<typeof ResetPasswordSchema>;\nexport type ChangePasswordType = z.infer<typeof ChangePasswordSchema>;\nexport type UpdateProfileType = z.infer<typeof UpdateProfileSchema>;\nexport type GoogleSigninType = z.infer<typeof GoogleSigninSchema>;\nexport type DeleteAccountType = z.infer<typeof DeleteAccountSchema>;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/utils/shutdown.ts",
                          "content": "import { Server } from \"http\";\nimport { logger } from \"./logger\";\n\nexport const configureGracefulShutdown = (server: Server) => {\n  const signals = [\"SIGTERM\", \"SIGINT\"];\n\n  signals.forEach(signal => {\n    process.on(signal, () => {\n      logger.info(`\\n${signal} signal received. Shutting down gracefully...`);\n\n      server.close(err => {\n        if (err) {\n          logger.error(err, \"Error during server close\");\n          process.exit(1);\n        }\n\n        logger.info(\"HTTP server closed.\");\n        process.exit(0);\n      });\n\n      // Force shutdown after 10 seconds\n      setTimeout(() => {\n        logger.error(\n          \"Could not close connections in time, forcefully shutting down\"\n        );\n        process.exit(1);\n      }, 10000);\n    });\n  });\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/utils/send-mail.ts",
                          "content": "import env from \"../configs/env\";\nimport { resend } from \"../configs/resend\";\n\nexport type SendMail = {\n  from?: string;\n  subject: string;\n  data: Record<string, any>;\n  email: string;\n  html: string;\n};\n\nexport async function sendEmail({ from, email, subject, html }: SendMail) {\n  return await resend.emails.send({\n    from: from || `<${env.EMAIL_FROM}>`,\n    to: email,\n    subject,\n    replyTo: email,\n    html\n  });\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/utils/logger.ts",
                          "content": "import pino from \"pino\";\nimport env from \"../configs/env\";\n\nexport const logger = pino({\n  level: env.LOG_LEVEL,\n  transport:\n    env.NODE_ENV !== \"production\"\n      ? {\n          target: \"pino-pretty\",\n          options: {\n            colorize: true,\n            translateTime: \"yyyy-mm-dd HH:MM:ss\",\n            ignore: \"pid,hostname\"\n          }\n        }\n      : undefined\n});\n"
                        },
                        {
                          "type": "file",
                          "path": "src/utils/async-handler.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\n\nexport type AsyncRouteHandler = (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => Promise<unknown>;\n\nexport function AsyncHandler(fn: AsyncRouteHandler) {\n  return function (req: Request, res: Response, next: NextFunction) {\n    Promise.resolve()\n      .then(() => fn(req, res, next))\n      .catch(next);\n  };\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/utils/api-response.ts",
                          "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\nimport type { Response } from \"express\";\n\ntype ApiResponseParams<T> = {\n  success: boolean;\n  message: string;\n  statusCode: StatusCode;\n  data?: T | null;\n  errors?: unknown;\n};\n\nexport class ApiResponse<T = unknown> {\n  public readonly success: boolean;\n  public readonly message: string;\n  public readonly statusCode: StatusCode;\n  public readonly data?: T | null;\n  public readonly errors?: unknown;\n\n  constructor({\n    success,\n    message,\n    statusCode,\n    data,\n    errors\n  }: ApiResponseParams<T>) {\n    this.success = success;\n    this.message = message;\n    this.statusCode = statusCode;\n    this.data = data;\n    this.errors = errors;\n  }\n\n  send(res: Response): Response {\n    return res.status(this.statusCode).json({\n      success: this.success,\n      message: this.message,\n      statusCode: this.statusCode,\n      ...(this.data !== undefined && { data: this.data }),\n      ...(this.errors !== undefined && { errors: this.errors })\n    });\n  }\n\n  static Success<T>(\n    res: Response,\n    message: string,\n    data?: T,\n    statusCode: StatusCode = STATUS_CODES.OK\n  ): Response {\n    return new ApiResponse<T>({\n      success: true,\n      message,\n      data,\n      statusCode\n    }).send(res);\n  }\n\n  static ok<T>(res: Response, message = \"OK\", data?: T) {\n    return ApiResponse.Success(res, message, data, STATUS_CODES.OK);\n  }\n\n  static created<T>(res: Response, message = \"Created\", data?: T) {\n    return ApiResponse.Success(res, message, data, STATUS_CODES.CREATED);\n  }\n}\n\n/*\n * Usage:\n * ApiResponse.ok(res, \"OK\", data);\n * ApiResponse.created(res, \"Created\", data);\n */\n"
                        },
                        {
                          "type": "file",
                          "path": "src/utils/api-error.ts",
                          "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\n\nexport class ApiError extends Error {\n  public readonly statusCode: StatusCode;\n  public readonly isOperational: boolean;\n  public readonly errors?: unknown;\n\n  constructor(\n    statusCode: StatusCode,\n    message: string,\n    errors?: unknown,\n    isOperational = true\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n    this.statusCode = statusCode;\n    this.errors = errors;\n    this.isOperational = isOperational;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n\n  static badRequest(message = \"Bad Request\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static unauthorized(message = \"Unauthorized\") {\n    return new ApiError(STATUS_CODES.UNAUTHORIZED, message);\n  }\n\n  static forbidden(message = \"Forbidden\") {\n    return new ApiError(STATUS_CODES.FORBIDDEN, message);\n  }\n\n  static notFound(message = \"Not Found\") {\n    return new ApiError(STATUS_CODES.NOT_FOUND, message);\n  }\n\n  static conflict(message = \"Conflict\") {\n    return new ApiError(STATUS_CODES.CONFLICT, message);\n  }\n\n  static server(message = \"Internal Server Error\") {\n    return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);\n  }\n\n  static unprocessableEntity(message = \"Unprocessable Entity\") {\n    return new ApiError(STATUS_CODES.UNPROCESSABLE_ENTITY, message);\n  }\n\n  static tooManyRequests(message = \"Too Many Requests\") {\n    return new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, message);\n  }\n}\n\n/*\n  ? Usage:\n  * throw new ApiError(404, \"Not found\");\n  * throw ApiError.badRequest(\"Bad request\");\n */\n"
                        },
                        {
                          "type": "file",
                          "path": "src/types/user.d.ts",
                          "content": "import { Request } from \"express\";\nimport { OTP_TYPES } from \"../constants/auth\";\n\nexport type OTPType = (typeof OTP_TYPES)[number];\n\nexport interface UserRequest extends Request {\n  user?: {\n    _id?: string;\n    role?: \"user\" | \"admin\" | undefined;\n  };\n  session?: {\n    _id?: string;\n    token?: string;\n  };\n}\n\nexport interface IUser {\n  _id: string;\n  name: string;\n  email: string;\n  password?: string;\n  role: \"user\" | \"admin\";\n  isEmailVerified: boolean;\n  lastLoginAt?: Date;\n  failedLoginAttempts: number;\n  lockUntil?: Date;\n  avatar?: {\n    url: string;\n    publicId: string;\n    size: number;\n  };\n  provider: \"local\" | \"google\" | \"github\";\n  providerId?: string;\n  isDeleted: boolean;\n  deletedAt?: Date;\n  reActivateAvailableAt?: Date;\n  createdAt: Date;\n  updatedAt: Date;\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/verify-auth.ts",
                          "content": "import { NextFunction, Response } from \"express\";\n\nimport { ApiError } from \"../utils/api-error\";\nimport { logger } from \"../utils/logger\";\nimport User from \"../models/user.model\";\nimport { UserRequest } from \"../types/user\";\nimport { generateHashedToken } from \"../helpers/token.helpers\";\nimport Session from \"../models/session.model\";\nimport { SESSION_EXPIRES_IN } from \"../constants/auth\";\n\nexport async function verifyAuthentication(\n  req: UserRequest,\n  res: Response,\n  next: NextFunction\n): Promise<void> {\n  try {\n    const sid = req.cookies?.sid;\n    const authorization = req.headers.authorization;\n    const token = authorization?.split(\" \")[1];\n\n    if (!sid && !token) {\n      return next(ApiError.unauthorized(\"Unauthorized, please login.\"));\n    }\n\n    const hashedSession = generateHashedToken(sid || token);\n\n    const session = await Session.findOne({\n      tokenHash: hashedSession,\n      isActive: true,\n      expiresAt: { $gt: new Date() }\n    });\n\n    if (!session) {\n      return next(ApiError.unauthorized(\"Session expired.\"));\n    }\n\n    if (\n      session.userAgent !== req.headers[\"user-agent\"] ||\n      session.ip !== req.ip\n    ) {\n      return next(ApiError.unauthorized(\"Invalid session.\"));\n    }\n\n    const user = await User.findById(session.userId);\n\n    if (!user) {\n      return next(ApiError.unauthorized(\"User not found.\"));\n    }\n    req.user = {\n      _id: user._id.toString(),\n      role: user.role\n    };\n\n    req.session = {\n      _id: session._id.toString(),\n      token: sid || token\n    };\n\n    const remainingTime = session.expiresAt.getTime() - Date.now();\n    const EXTEND_THRESHOLD = SESSION_EXPIRES_IN * 0.25;\n\n    if (remainingTime < EXTEND_THRESHOLD) {\n      await Session.updateOne(\n        { _id: session._id.toString() },\n        {\n          $set: {\n            lastUsedAt: new Date(),\n            expiresAt: new Date(Date.now() + SESSION_EXPIRES_IN)\n          }\n        }\n      );\n    }\n\n    return next();\n  } catch (error) {\n    logger.error(error, \"Authentication verification failed\");\n    return next(\n      ApiError.server(\"Internal server error during authentication.\")\n    );\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/validate-request.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\nimport z, { ZodError, type ZodObject } from \"zod\";\n\nimport { ApiError } from \"../utils/api-error\";\n\nexport const validateRequest = (schema: ZodObject<any>) => {\n  return (req: Request, res: Response, next: NextFunction) => {\n    try {\n      schema.parse(req.body);\n\n      next();\n    } catch (error) {\n      if (!(error instanceof ZodError)) {\n        return next(error);\n      }\n\n      return next(\n        ApiError.badRequest(\n          \"Invalid request data\",\n          z.flattenError(error).fieldErrors || z.flattenError(error)\n        )\n      );\n    }\n  };\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/validate-id.ts",
                          "content": "import { isValidObjectId } from \"mongoose\";\nimport { ApiError } from \"../utils/api-error\";\nimport { NextFunction, Request, Response } from \"express\";\n\nexport const validateObjectId = (paramName: string = \"id\") => {\n  return (req: Request, res: Response, next: NextFunction) => {\n    const value =\n      req?.params[paramName] || req?.body[paramName] || req?.query[paramName];\n    if (!value) {\n      throw ApiError.badRequest(`${paramName} is required`);\n    }\n\n    if (!isValidObjectId(value)) {\n      throw ApiError.badRequest(`Invalid ${paramName}`);\n    }\n\n    next();\n  };\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/user-account-restriction.ts",
                          "content": "import { NextFunction, Response } from \"express\";\nimport { UserRequest } from \"../types/user\";\nimport User from \"../models/user.model\";\nimport { ApiError } from \"../utils/api-error\";\nimport { logger } from \"../utils/logger\";\n\nexport async function checkUserAccountRestriction(\n  req: UserRequest,\n  _res: Response,\n  next: NextFunction\n): Promise<void> {\n  try {\n    if (!req.user?._id) {\n      return next(ApiError.unauthorized(\"Unauthorized\"));\n    }\n\n    const user = await User.findById(req.user._id);\n\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized, please login.\"));\n    }\n\n    if (user.isDeleted || user.deletedAt) {\n      return next(ApiError.forbidden(\"Your account has been deactivated.\"));\n    }\n\n    if (user.lockUntil && user.lockUntil.getTime() > Date.now()) {\n      const minutesLeft = Math.ceil(\n        (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n      );\n\n      return next(\n        ApiError.forbidden(\n          `Your account has been locked. Please try again after ${minutesLeft} minutes.`\n        )\n      );\n    }\n\n    if (!user.isEmailVerified) {\n      return next(\n        ApiError.forbidden(\"Email not verified. Please verify your email.\")\n      );\n    }\n\n    return next();\n  } catch (err: any) {\n    logger.error(err?.message || err);\n    return next(ApiError.server(\"Something went wrong\"));\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/upload-file.ts",
                          "content": "import multer from \"multer\";\n\nexport const ALLOWED_FILE_TYPES = [\n  \"image/jpeg\",\n  \"image/png\",\n  \"image/webp\",\n  \"video/mp4\",\n  \"video/mpeg\",\n  \"video/quicktime\",\n  \"application/pdf\"\n];\n\nexport const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB\n\nconst storage = multer.memoryStorage();\n\nconst fileFilter: multer.Options[\"fileFilter\"] = (_req, file, cb) => {\n  if (!ALLOWED_FILE_TYPES.includes(file.mimetype)) {\n    return cb(null, false);\n  }\n  cb(null, true);\n};\n\nconst upload = multer({\n  storage,\n  limits: { fileSize: MAX_FILE_SIZE },\n  fileFilter\n});\n\nexport default upload;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/security-header.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport cors from \"cors\";\nimport { Express } from \"express\";\nimport helmet from \"helmet\";\nimport env from \"../configs/env\";\n\nexport const configureSecurityHeaders = (app: Express) => {\n  // Use Helmet to set various security-related HTTP headers\n  app.use(helmet());\n\n  // Configure CORS\n  app.use(\n    cors({\n      origin: env.CORS_ORIGIN || \"*\",\n      credentials: true,\n      methods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"],\n      allowedHeaders: [\"Content-Type\", \"Authorization\", \"X-Requested-With\"]\n    })\n  );\n\n  // Additional custom security headers\n  app.use((req: Request, res: Response, next: NextFunction) => {\n    res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n    res.setHeader(\"X-Frame-Options\", \"DENY\");\n    res.setHeader(\"X-XSS-Protection\", \"1; mode=block\");\n    next();\n  });\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/rate-limiter.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport { rateLimit } from \"express-rate-limit\";\nimport { STATUS_CODES } from \"../constants/status-codes\";\nimport { ApiError } from \"../utils/api-error\";\n\nexport const rateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // Limit each IP to 100 requests per window\n  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers\n  legacyHeaders: false, // Disable the `X-RateLimit-*` headers\n  message: {\n    success: false,\n    message:\n      \"Too many requests from this IP, please try again after 15 minutes\",\n    status: 429\n  },\n  handler: (req: Request, res: Response, next: NextFunction, options: any) => {\n    next(new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, options.message.message));\n  }\n});\n\n/**\n * Stricter rate limiter for sensitive routes (e.g., auth, login)\n */\nexport const authRateLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1 hour\n  max: 5, // Limit each IP to 5 failed attempts per hour\n  handler: (req, res, next, options) => {\n    next(\n      ApiError.tooManyRequests(\n        \"Too many login attempts, please try again after an hour\"\n      )\n    );\n  }\n});\n\n/**\n * Rate limiter for login route\n */\nexport const signinRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many login attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\n/**\n * Rate limiter for registration route\n */\nexport const signupRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many registration attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpRequestLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP requests. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpVerificationLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP verification attempts. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const resetPasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many password reset attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const deleteAccountLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many account deletion attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const changePasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many password change attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/not-found-handler.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\nimport { ApiError } from \"../utils/api-error\";\n\nexport const notFoundHandler = (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  throw ApiError.notFound(`Route ${req.method} ${req.originalUrl} not found`);\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/middlewares/error-handler.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\nimport env from \"../configs/env\";\n\nimport { logger } from \"../utils/logger\";\nimport { ApiError } from \"../utils/api-error\";\n\nexport const errorHandler = (\n  err: Error,\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  if (res.headersSent) {\n    return next(err);\n  }\n  let statusCode = 500;\n  let message = \"Internal server error\";\n  let errors: unknown;\n\n  if (err instanceof ApiError) {\n    statusCode = err.statusCode;\n    message = err.message;\n    errors = err.errors;\n  }\n\n  logger.error(\n    err,\n    `Error: ${message} | Status: ${statusCode} | Path: ${req.method} ${req.originalUrl}`\n  );\n\n  const response = {\n    success: false,\n    message,\n    statusCode,\n    ...(errors !== undefined && { errors }),\n    ...(env.NODE_ENV === \"development\" && { stack: err.stack })\n  };\n\n  res.status(statusCode).json(response);\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/models/user.model.ts",
                          "content": "import mongoose, { Document, Model, Schema } from \"mongoose\";\n\nexport interface IAvatar {\n  public_id: string;\n  url: string;\n  size: number;\n}\n\nexport interface IUser extends Document {\n  _id: mongoose.Types.ObjectId;\n  name: string;\n  email: string;\n  password?: string;\n  role: \"user\" | \"admin\";\n  isEmailVerified: boolean;\n  lastLoginAt?: Date;\n  failedLoginAttempts: number;\n  lockUntil?: Date;\n  avatar?: IAvatar;\n\n  provider: \"local\" | \"google\" | \"github\";\n  providerId?: string;\n\n  isDeleted: boolean;\n  deletedAt?: Date | null;\n  reActivateAvailableAt?: Date | null;\n\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nconst userSchema = new Schema<IUser>(\n  {\n    name: {\n      type: String,\n      required: [true, \"Name is required\"],\n      trim: true\n    },\n    email: {\n      type: String,\n      required: [true, \"Email is required\"],\n      unique: true,\n      lowercase: true,\n      trim: true\n    },\n    password: {\n      type: String,\n      select: false,\n      default: null\n    },\n    provider: {\n      type: String,\n      enum: [\"local\", \"google\", \"github\"],\n      default: \"local\"\n    },\n    providerId: {\n      type: String,\n      default: null\n    },\n    role: {\n      type: String,\n      enum: [\"user\", \"admin\"],\n      default: \"user\"\n    },\n    avatar: {\n      public_id: String,\n      url: String,\n      size: Number\n    },\n    isEmailVerified: {\n      type: Boolean,\n      default: false\n    },\n    lastLoginAt: {\n      type: Date\n    },\n    failedLoginAttempts: {\n      type: Number,\n      required: true,\n      default: 0\n    },\n    lockUntil: {\n      type: Date\n    },\n    isDeleted: {\n      type: Boolean,\n      default: false\n    },\n    deletedAt: {\n      type: Date,\n      default: null\n    },\n    reActivateAvailableAt: {\n      type: Date,\n      default: null\n    }\n  },\n  {\n    timestamps: true\n  }\n);\n\n// Performance Indexes\nuserSchema.index({ provider: 1, providerId: 1 }); // Quick lookup for OAuth\nuserSchema.index({ role: 1 });\nuserSchema.index({ isDeleted: 1 }); // Optimized for soft-delete queries\n\nconst User: Model<IUser> =\n  mongoose.models.User || mongoose.model<IUser>(\"User\", userSchema);\n\nexport default User;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/models/session.model.ts",
                          "content": "import mongoose, { Document, Schema, Model } from \"mongoose\";\nimport { SESSION_EXPIRES_IN } from \"../constants/auth\";\n\nexport interface ISession extends Document {\n  _id: mongoose.Types.ObjectId;\n  userId: mongoose.Types.ObjectId;\n  tokenHash: string;\n  ip?: string;\n  userAgent?: string;\n  isActive: boolean;\n  lastUsedAt: Date;\n  expiresAt: Date;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nconst sessionSchema: Schema<ISession> = new Schema(\n  {\n    userId: {\n      type: mongoose.Schema.Types.ObjectId,\n      ref: \"User\",\n      required: true\n    },\n    tokenHash: {\n      type: String,\n      required: true,\n      unique: true\n    },\n    ip: {\n      type: String\n    },\n    userAgent: {\n      type: String\n    },\n    isActive: {\n      type: Boolean,\n      default: true\n    },\n    lastUsedAt: {\n      type: Date,\n      default: Date.now\n    },\n    expiresAt: {\n      type: Date,\n      required: true\n    }\n  },\n  {\n    timestamps: true\n  }\n);\n\n// Supporting indexes (non-TTL)\nsessionSchema.index({ userId: 1, isActive: 1 });\nsessionSchema.index({ userId: 1, lastUsedAt: -1 });\nsessionSchema.index(\n  { createdAt: 1 },\n  { expireAfterSeconds: SESSION_EXPIRES_IN / 1000 } // 7 days\n);\n\nconst Session: Model<ISession> =\n  mongoose.models.Session || mongoose.model<ISession>(\"Session\", sessionSchema);\n\nexport default Session;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/models/otp.model.ts",
                          "content": "import mongoose, { Document, Model, Schema } from \"mongoose\";\nimport { OTPType } from \"../types/user\";\nimport { OTP_EXPIRES_IN, OTP_MAX_ATTEMPTS, OTP_TYPES } from \"../constants/auth\";\n\n//? otp interface\nexport interface IOtp extends Document {\n  _id: mongoose.Types.ObjectId;\n  email: string;\n  otpHashCode: string;\n  nextResendAllowedAt: Date;\n  type: OTPType;\n  expiresAt: Date;\n  isUsed: boolean;\n  usedAt?: Date;\n  attempts: number;\n  maxAttempts: number;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\n//? otp schema\nconst otpSchema = new Schema<IOtp>(\n  {\n    email: {\n      type: String,\n      required: [true, \"Email is required\"],\n      lowercase: true,\n      trim: true\n    },\n    otpHashCode: {\n      type: String,\n      required: [true, \"OTP hash code is required\"],\n      select: false // Never return OTP hash code in queries by default\n    },\n    nextResendAllowedAt: {\n      type: Date,\n      required: [true, \"Next resend allowed at is required\"]\n    },\n    type: {\n      type: String,\n      enum: OTP_TYPES,\n      required: [true, \"OTP type is required\"]\n    },\n    expiresAt: {\n      type: Date,\n      required: [true, \"Expiration time is required\"]\n    },\n    isUsed: {\n      type: Boolean,\n      default: false\n    },\n    usedAt: {\n      type: Date\n    },\n    attempts: {\n      type: Number,\n      default: 0\n    },\n    maxAttempts: {\n      type: Number,\n      default: OTP_MAX_ATTEMPTS // Prevent brute force attacks\n    }\n  },\n  {\n    timestamps: true\n  }\n);\n\n// Performance Indexes\notpSchema.index({ email: 1, type: 1 }); // Quick lookup by email and type\n\notpSchema.index(\n  { createdAt: 1 },\n  { expireAfterSeconds: OTP_EXPIRES_IN / 1000 } // 5 minutes\n); // ttl index\n\nconst Otp: Model<IOtp> =\n  mongoose.models.Otp || mongoose.model<IOtp>(\"Otp\", otpSchema);\n\nexport default Otp;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/routes/oauth.routes.ts",
                          "content": "import { Router } from \"express\";\nimport passport from \"passport\";\nimport { githubOAuth, googleOAuth } from \"../controllers/oauth.controller\";\n\nconst router = Router();\n\nrouter.get(\n  \"/github\",\n  passport.authenticate(\"github\", { scope: [\"user:email\"] })\n);\n\nrouter.get(\n  \"/github/callback\",\n  passport.authenticate(\"github\", {\n    failureRedirect: \"/login\", //? redirect route if authenticated is failed,\n    session: false\n  }),\n  githubOAuth\n);\n\nrouter.get(\n  \"/google\",\n  passport.authenticate(\"google\", {\n    scope: [\"email\", \"profile\", \"openid\"],\n    prompt: \"consent\"\n  })\n);\n\nrouter.get(\n  \"/google/callback\",\n  passport.authenticate(\"google\", {\n    failureRedirect: \"/login\", //? redirect route if authenticated is failed\n    session: false\n  }),\n  googleOAuth\n);\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/routes/index.ts",
                          "content": "import { Router } from \"express\";\nimport healthRoutes from \"./health.routes\";\nimport authRoutes from \"./auth.routes\";\nimport oauthRoutes from \"./oauth.routes\";\n\nconst router = Router();\n\nrouter.use(\"/v1/health\", healthRoutes);\nrouter.use(\"/v1/auth\", authRoutes);\nrouter.use(\"/auth\", oauthRoutes); //* Here versioning is not given because, in google and github callback routes, we are not using versioning. process.env.GOOGLE_REDIRECT_URI\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/routes/health.routes.ts",
                          "content": "import { Router } from \"express\";\nimport {\n  healthCheck,\n  detailedHealthCheck\n} from \"../controllers/health.controller\";\n\nconst router = Router();\n\nrouter.get(\"/\", healthCheck);\nrouter.get(\"/detailed\", detailedHealthCheck);\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/routes/auth.routes.ts",
                          "content": "import { Router } from \"express\";\nimport { validateRequest } from \"../middlewares/validate-request\";\nimport {\n  ChangePasswordSchema,\n  DeleteAccountSchema,\n  RequestOtpSchema,\n  ResetPasswordSchema,\n  SigninSchema,\n  SignupSchema,\n  UpdateProfileSchema,\n  VerifyOtpSchema\n} from \"../validators/auth\";\nimport {\n  changePassword,\n  deleteAccount,\n  deleteAllUserSessions,\n  deleteUserSession,\n  forgotPassword,\n  getUserProfile,\n  getUserSessions,\n  logoutUser,\n  reactivateAccount,\n  resetPassword,\n  signinUser,\n  signupUser,\n  updateProfile,\n  verifyOtp\n} from \"../controllers/auth.controller\";\nimport { verifyAuthentication } from \"../middlewares/verify-auth\";\nimport { checkUserAccountRestriction } from \"../middlewares/user-account-restriction\";\nimport {\n  changePasswordLimiter,\n  deleteAccountLimiter,\n  otpRequestLimiter,\n  otpVerificationLimiter,\n  resetPasswordLimiter,\n  signinRateLimiter,\n  signupRateLimiter\n} from \"../middlewares/rate-limiter\";\nimport upload from \"../middlewares/upload-file\";\nimport { validateObjectId } from \"../middlewares/validate-id\";\n\nconst router = Router();\n\nrouter.post(\n  \"/verify-otp\",\n  validateRequest(VerifyOtpSchema),\n  otpVerificationLimiter,\n  verifyOtp\n);\n\nrouter.post(\n  \"/signup\",\n  validateRequest(SignupSchema),\n  signupRateLimiter,\n  signupUser\n);\n\nrouter.post(\n  \"/signin\",\n  validateRequest(SigninSchema),\n  signinRateLimiter,\n  signinUser\n);\n\nrouter.get(\"/profile\", verifyAuthentication, getUserProfile);\n\nrouter.get(\"/sessions\", verifyAuthentication, getUserSessions);\n\nrouter.delete(\"/sessions\", verifyAuthentication, deleteAllUserSessions);\n\nrouter.delete(\n  \"/sessions/:sessionId\",\n  validateObjectId(\"sessionId\"),\n  verifyAuthentication,\n  deleteUserSession\n);\n\nrouter.patch(\n  \"/profile\",\n  upload.single(\"avatar\"),\n  validateRequest(UpdateProfileSchema),\n  verifyAuthentication,\n  checkUserAccountRestriction,\n  updateProfile\n);\n\nrouter.post(\n  \"/logout\",\n  verifyAuthentication,\n  checkUserAccountRestriction,\n  logoutUser\n);\n\nrouter.post(\n  \"/forgot-password\",\n  validateRequest(RequestOtpSchema.pick({ email: true })),\n  otpRequestLimiter,\n  forgotPassword\n);\n\nrouter.post(\n  \"/reset-password\",\n  validateRequest(ResetPasswordSchema),\n  resetPasswordLimiter,\n  resetPassword\n);\n\nrouter.post(\n  \"/change-password\",\n  verifyAuthentication,\n  validateRequest(ChangePasswordSchema),\n  checkUserAccountRestriction,\n  changePasswordLimiter,\n  changePassword\n);\n\nrouter.delete(\n  \"/delete-account\",\n  verifyAuthentication,\n  validateRequest(DeleteAccountSchema),\n  checkUserAccountRestriction,\n  deleteAccountLimiter,\n  deleteAccount\n);\n\nrouter.put(\"/reactivate-account\", verifyAuthentication, reactivateAccount);\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/services/otp.service.ts",
                          "content": "import { NextFunction } from \"express\";\nimport User from \"../models/user.model\";\nimport { IUser, OTPType } from \"../types/user\";\nimport { ApiError } from \"../utils/api-error\";\nimport Otp from \"../models/otp.model\";\nimport {\n  NEXT_OTP_DELAY,\n  OTP_CODE_LENGTH,\n  OTP_EXPIRES_IN,\n  OTP_MAX_ATTEMPTS,\n  RESET_PASSWORD_TOKEN_EXPIRY\n} from \"../constants/auth\";\nimport { logger } from \"../utils/logger\";\nimport { generateHashedToken, generateOTP } from \"../helpers/token.helpers\";\nimport { AuthService } from \"./auth.service\";\nimport { sendEmail, SendMail } from \"../utils/send-mail\";\nimport env from \"../configs/env\";\n\ntype VerifyOtpPayload = {\n  email: string;\n  otpCode: string;\n  otpType: OTPType;\n};\n\ntype VerifyOtpContext = {\n  setAuthCookie?: (token: string) => void;\n  ip?: string;\n  userAgent?: string;\n};\n\ntype ResetPassowrdContext = {\n  setCookie?: (token: string) => void;\n};\n\ntype SendOtpPayload = {\n  email: string;\n  otpType: OTPType;\n  subject: string;\n};\n\nexport class OtpService {\n  static async sendOtp(next: NextFunction, payload: SendOtpPayload) {\n    const { email, otpType, subject } = payload;\n\n    const user = await User.findOne({ email });\n    if (!user) {\n      return next(ApiError.badRequest(\"Invalid request\"));\n    }\n\n    if (user.lockUntil && user.lockUntil > new Date()) {\n      return next(ApiError.badRequest(\"Account locked\"));\n    }\n\n    const existingOtp = await Otp.findOne({ email, type: otpType });\n\n    if (existingOtp && existingOtp.nextResendAllowedAt > new Date()) {\n      const remainingSec = Math.ceil(\n        (existingOtp.nextResendAllowedAt.getTime() - Date.now()) / 1000\n      );\n      return next(\n        ApiError.badRequest(\n          `Please wait ${remainingSec} seconds before requesting another OTP`\n        )\n      );\n    }\n\n    const otp = generateOTP(OTP_CODE_LENGTH, OTP_EXPIRES_IN);\n    logger.info(\n      `Sending OTP to ${email} with type ${otpType} and code ${otp.code}`\n    );\n    const nextResendAllowedAt = new Date(Date.now() + NEXT_OTP_DELAY);\n\n    let html = `<p>Your OTP for ${otpType}: ${otp.code}</p>`;\n    await sendEmail({\n      from: env.EMAIL_FROM,\n      email,\n      subject: subject,\n      html\n    } as SendMail);\n\n    await Otp.create({\n      email: payload.email,\n      type: payload.otpType,\n      otpHashCode: otp.hashCode,\n      attempts: 0,\n      isUsed: false,\n      expiresAt: otp.expiresAt,\n      nextResendAllowedAt\n    });\n\n    return { message: `OTP sent to ${email} successfully` };\n  }\n\n  static async verifyOtp(\n    next: NextFunction,\n    payload: VerifyOtpPayload,\n    context: VerifyOtpContext,\n    resetPasswordContext: ResetPassowrdContext\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ): Promise<any> {\n    const { email, otpCode, otpType } = payload;\n\n    const user = await User.findOne({ email });\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized, Please login first.\"));\n    }\n\n    if (user.lockUntil && user.lockUntil > new Date()) {\n      const minutes = Math.ceil(\n        (user.lockUntil.getTime() - Date.now()) / 60000\n      );\n      return next(\n        ApiError.badRequest(\n          `Your account has been locked. Try again in ${minutes} minutes.`\n        )\n      );\n    }\n\n    const otp = await Otp.findOne({\n      email,\n      type: otpType,\n      isUsed: false,\n      expiresAt: { $gt: new Date() }\n    })\n      .sort({ createdAt: -1 })\n      .select(\"+otpHashCode\");\n\n    if (!otp) {\n      return next(ApiError.badRequest(\"Invalid or expired OTP\"));\n    }\n\n    if (otp.attempts >= (otp.maxAttempts || OTP_MAX_ATTEMPTS)) {\n      return next(ApiError.badRequest(\"Maximum OTP attempts reached\"));\n    }\n\n    const hashedOtp = generateHashedToken(String(otpCode));\n\n    if (otp.otpHashCode !== hashedOtp) {\n      await Otp.updateOne({ _id: otp._id }, { $inc: { attempts: 1 } });\n      return next(ApiError.badRequest(\"Invalid OTP code\"));\n    }\n\n    otp.isUsed = true;\n    await otp.save();\n\n    await Otp.deleteOne({ _id: otp._id });\n    await Otp.deleteMany({\n      $or: [{ expiresAt: { $lt: new Date() }, isUsed: true }]\n    });\n    if (otp.type === \"signin\") {\n      return await AuthService.handleToken(\n        {\n          _id: user._id.toString(),\n          role: user.role,\n          isEmailVerified: user.isEmailVerified,\n          email\n        },\n        context\n      );\n    }\n\n    if (otp.type === \"password-reset\") {\n      return this.handlePasswordReset(\n        {\n          _id: user._id.toString()\n        },\n        resetPasswordContext\n      );\n    }\n\n    return { message: \"OTP verified successfully\" };\n  }\n\n  private static handlePasswordReset(\n    user: Pick<IUser, \"_id\">,\n    context: ResetPassowrdContext\n  ) {\n    const hashedResetPasswordToken = generateHashedToken(user._id.toString());\n    const resetPasswordExpiry = new Date(\n      Date.now() + RESET_PASSWORD_TOKEN_EXPIRY\n    );\n\n    context.setCookie && context.setCookie(hashedResetPasswordToken);\n\n    return {\n      hashedResetPasswordToken,\n      resetPasswordExpiry\n    };\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/services/oauth.service.ts",
                          "content": "import { AuthService, Context } from \"./auth.service\";\nimport User, { IUser } from \"../models/user.model\";\n\ntype OAuthProfile = {\n  provider: string;\n  providerId: string;\n  name: string;\n  email: string | undefined;\n  isEmailVerified: boolean;\n  avatar: string | undefined;\n};\n\nexport class OAuthService {\n  static async handleOAuthLogin(user: OAuthProfile, context: Context) {\n    const existingUser = await User.findOne({ email: user.email });\n\n    if (existingUser) {\n      await User.findByIdAndUpdate(existingUser._id, {\n        provider: user.provider,\n        providerId: user.providerId,\n        isEmailVerified: user.isEmailVerified,\n        avatar: {\n          url: user.avatar\n        }\n      });\n      await AuthService.handleToken(\n        {\n          _id: existingUser._id.toString(),\n          role: existingUser.role,\n          isEmailVerified: existingUser.isEmailVerified,\n          email: existingUser.email\n        },\n        context\n      );\n      return existingUser;\n    }\n\n    const newUser = await User.create({\n      name: user.name,\n      email: user.email,\n      isEmailVerified: user.isEmailVerified,\n\n      provider: user.provider,\n      providerId: user.providerId,\n\n      avatar: {\n        url: user.avatar\n      }\n    });\n\n    await AuthService.handleToken(\n      {\n        _id: newUser._id.toString(),\n        role: newUser.role,\n        isEmailVerified: newUser.isEmailVerified,\n        email: newUser.email\n      },\n      context\n    );\n\n    return newUser;\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/services/cloudinary.service.ts",
                          "content": "import { DeleteApiResponse } from \"cloudinary\";\nimport cloudinary from \"../configs/cloudinary\";\n\nexport interface UploadOptions {\n  folder: string;\n  resource_type?: \"image\" | \"video\" | \"raw\" | \"auto\";\n}\n\nexport interface CloudinaryUploadResult {\n  url: string;\n  public_id: string;\n  size: number;\n}\n\nexport const uploadToCloudinary = (\n  buffer: Buffer,\n  options: UploadOptions\n): Promise<CloudinaryUploadResult> => {\n  return new Promise((resolve, reject) => {\n    const stream = cloudinary.uploader.upload_stream(\n      {\n        folder: options.folder || \"uploads\",\n        resource_type: options.resource_type || \"auto\"\n      },\n      (error, result) => {\n        if (error || !result) {\n          return reject(error);\n        }\n        resolve({\n          url: result.secure_url,\n          public_id: result.public_id,\n          size: result.bytes\n        });\n      }\n    );\n\n    stream.end(buffer);\n  });\n};\n\nexport const deleteFileFromCloudinary = (\n  publicIds: string[]\n): Promise<DeleteApiResponse> => {\n  return new Promise((resolve, reject) => {\n    cloudinary.api.delete_resources(publicIds, (error, result) => {\n      if (error || !result) {\n        return reject(error);\n      }\n      resolve(result);\n    });\n  });\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/services/auth.service.ts",
                          "content": "import { NextFunction } from \"express\";\nimport User from \"../models/user.model\";\nimport { ApiError } from \"../utils/api-error\";\nimport { hashPassword, verifyPassword } from \"../helpers/auth.helpers\";\nimport { SignupUserType } from \"../validators/auth\";\nimport {\n  LOGIN_MAX_ATTEMPTS,\n  MAX_SESSIONS_PER_USER,\n  REACTIVATION_AVAILABLE_AT,\n  SESSION_EXPIRES_IN\n} from \"../constants/auth\";\n\nimport {\n  generateHashedToken,\n  generateSecureToken,\n  generateTokenAndHashedToken\n} from \"../helpers/token.helpers\";\nimport { IUser } from \"../types/user\";\nimport mongoose from \"mongoose\";\nimport { OtpService } from \"./otp.service\";\nimport { deleteFileFromCloudinary } from \"./cloudinary.service\";\nimport Session from \"../models/session.model\";\nimport { sendEmail, SendMail } from \"../utils/send-mail\";\nimport env from \"../configs/env\";\n\nexport type Context = {\n  setAuthCookie?: (token: string) => void;\n  ip?: string;\n  userAgent?: string;\n};\n\nexport class AuthService {\n  static async registerUser(\n    next: NextFunction,\n    user: Omit<SignupUserType, \"confirmPassword\">\n  ) {\n    const { name, email, password, role } = user;\n    const existingUser = await User.findOne({ email }).select(\"+password\");\n\n    if (existingUser) {\n      return next(ApiError.conflict(\"User with this email already exists\"));\n    }\n\n    const hashedPassword = await hashPassword(password);\n\n    const newUser = await User.create({\n      name,\n      email,\n      password: hashedPassword,\n      role\n    });\n\n    return newUser;\n  }\n\n  static async loginAndSendOtp(\n    next: NextFunction,\n    { email, password }: { email: string; password: string }\n  ) {\n    const session = await mongoose.startSession();\n\n    try {\n      session.startTransaction();\n\n      const user = await User.findOne({ email })\n        .session(session)\n        .select(\"+password\");\n      if (!user) {\n        await session.abortTransaction();\n        return next(ApiError.unauthorized(\"Invalid credentials\"));\n      }\n\n      const isPasswordValid = await verifyPassword(\n        password,\n        user.password || \"\"\n      );\n      if (!isPasswordValid) {\n        await session.abortTransaction();\n        return next(ApiError.unauthorized(\"Invalid credentials\"));\n      }\n\n      const otp = await OtpService.sendOtp(next, {\n        email,\n        otpType: \"signin\",\n        subject: \"Signin\"\n      });\n\n      if (!otp) {\n        await session.abortTransaction();\n        return next(ApiError.server(\"Failed to generate OTP\"));\n      }\n\n      await session.commitTransaction();\n      session.endSession();\n\n      return {\n        message: otp.message\n      };\n    } catch (err) {\n      await session.abortTransaction();\n      session.endSession();\n      return next(ApiError.server(\"Signin failed\"));\n    }\n  }\n\n  static async handleToken(\n    user: Pick<IUser, \"isEmailVerified\" | \"_id\" | \"role\" | \"email\">,\n    context: Context\n  ) {\n    if (!user.isEmailVerified) {\n      await User.updateOne(\n        { _id: user._id },\n        { $set: { isEmailVerified: true } }\n      );\n    }\n\n    if (context.userAgent) {\n      const existingDevice = await Session.findOne({\n        userId: user._id,\n        userAgent: context.userAgent\n      });\n\n      const isNewDevice = !existingDevice;\n      if (isNewDevice) {\n        const html = `\n        <p>New sign-in to your account from a new device or browser.</p>\n        <p>This could be a sign of a possible security threat.</p>\n        <p> \n          <strong>Device:</strong> ${context.userAgent}<br/>\n          <strong>IP Address:</strong> ${context.ip || \"Unknown\"}<br/>\n          <strong>Time:</strong> ${new Date().toDateString()}<br/>\n        </p>\n        <p>If this was you, you can safely ignore this email. If not, please secure your account immediately.</p>\n        `;\n\n        await sendEmail({\n          from: env.EMAIL_FROM,\n          email: user.email,\n          subject: \"New Sign-in Alert\",\n          html\n        } as SendMail);\n      }\n    }\n\n    const token = generateSecureToken();\n    const hashedToken = generateHashedToken(token);\n    const activeSessions = await Session.countDocuments({\n      userId: user._id,\n      isActive: true\n    });\n\n    if (activeSessions >= MAX_SESSIONS_PER_USER) {\n      await Session.findOneAndUpdate(\n        { userId: user._id, isActive: true },\n        { isActive: false },\n        { sort: { lastUsedAt: 1 } }\n      );\n    }\n\n    await Session.create({\n      userId: user._id,\n      tokenHash: hashedToken,\n      expiresAt: new Date(Date.now() + SESSION_EXPIRES_IN),\n      ip: context.ip,\n      userAgent: context.userAgent,\n      isActive: true\n    });\n\n    context.setAuthCookie && context.setAuthCookie(token);\n\n    await User.updateOne(\n      { _id: user._id },\n      {\n        $set: { lastLogin: new Date(), failedLoginAttempts: 0 },\n        $unset: { lockUntil: 1 }\n      }\n    );\n    return { message: \"OTP verified and user signed in successfully\" };\n  }\n\n  static async getUserProfile(userId: string) {\n    const user = await User.findById(userId);\n    return user;\n  }\n\n  static async forgotPassword(next: NextFunction, email: string) {\n    const user = await User.findOne({ email });\n\n    if (!user) {\n      return next(\n        ApiError.badRequest(\"If this email is registered, check your inbox.\")\n      );\n    }\n\n    const result = await OtpService.sendOtp(next, {\n      email,\n      otpType: \"password-reset\",\n      subject: \"Password Reset\"\n    });\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to send otp!\"));\n    }\n\n    return result;\n  }\n\n  static async resetPassword(\n    next: NextFunction,\n    email: string,\n    newPassword: string\n  ) {\n    const user = await User.findOne({ email }).select(\"+password\");\n\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (user.lockUntil && new Date(user.lockUntil) > new Date()) {\n      return next(\n        ApiError.forbidden(\n          `Your account has been locked. Please try again after ${Math.ceil(\n            (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    if (user.failedLoginAttempts >= LOGIN_MAX_ATTEMPTS && user.lockUntil) {\n      return next(\n        ApiError.forbidden(\n          `You have exceeded the maximum number of login attempts. Please try again after ${Math.ceil(\n            (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    if (!user.isEmailVerified) {\n      return next(ApiError.unauthorized(\"Please verify your email first.\"));\n    }\n\n    const oldPassword = user.password;\n\n    const isOldPassword = await verifyPassword(\n      newPassword,\n      oldPassword as string\n    );\n\n    if (isOldPassword) {\n      return next(ApiError.badRequest(\"New password should be different!\"));\n    }\n\n    const hashedPassword = await hashPassword(newPassword);\n    await User.updateOne(\n      { email },\n      {\n        $set: {\n          password: hashedPassword,\n          isEmailVerified: true\n        }\n      }\n    );\n    return { message: \"Password reset successfully!\" };\n  }\n\n  static async changePassword(\n    next: NextFunction,\n    {\n      newPassword,\n      oldPassword,\n      userId\n    }: { userId: string; newPassword: string; oldPassword: string }\n  ) {\n    const user = await User.findById(userId).select(\"+password\");\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (!user.isEmailVerified) {\n      return next(ApiError.unauthorized(\"Please verify your email first.\"));\n    }\n\n    const isOldPassword = await verifyPassword(\n      oldPassword,\n      user.password || \"\"\n    );\n\n    if (!isOldPassword) {\n      return next(ApiError.unauthorized(\"Invalid credentials\"));\n    }\n\n    if (newPassword === oldPassword) {\n      return next(ApiError.badRequest(\"New password should be different!\"));\n    }\n\n    const hashedPassword = await hashPassword(newPassword);\n    await User.updateOne(\n      { _id: userId },\n      {\n        $set: {\n          password: hashedPassword\n        }\n      }\n    );\n    return { message: \"Password changed successfully. Please login again!\" };\n  }\n\n  static async deleteOrDeactiveAccount(\n    next: NextFunction,\n    userId: string,\n    type: \"soft\" | \"hard\"\n  ) {\n    const user = await User.findById(userId);\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (type === \"soft\") {\n      user.isDeleted = true;\n      user.deletedAt = new Date();\n      user.reActivateAvailableAt = new Date(\n        Date.now() + REACTIVATION_AVAILABLE_AT\n      );\n      await user.save();\n    } else if (type === \"hard\") {\n      if (user?.avatar?.public_id) {\n        await deleteFileFromCloudinary([user.avatar.public_id]);\n      }\n      await User.findOneAndDelete({ _id: userId });\n      await user.save();\n    }\n  }\n\n  static async reactivateAccount(next: NextFunction, userId: string) {\n    const user = await User.findById(userId);\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (user.lockUntil && new Date(user.lockUntil) > new Date()) {\n      return next(\n        ApiError.badRequest(\n          `Your account has been locked. Please try again after ${Math.ceil(\n            (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    if (!user?.isDeleted || !user?.deletedAt) {\n      return next(ApiError.badRequest(\"Your account is already active!\"));\n    }\n\n    if (\n      user?.reActivateAvailableAt &&\n      new Date(user?.reActivateAvailableAt) > new Date()\n    ) {\n      return next(\n        ApiError.forbidden(\n          `Your account has been locked. Please try again after ${Math.ceil(\n            (user.reActivateAvailableAt.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    await User.findOneAndUpdate(\n      { _id: userId },\n      {\n        $set: {\n          isDeleted: false,\n          deletedAt: null,\n          reActivateAvailableAt: null\n        }\n      },\n      { new: true }\n    );\n\n    await user.save();\n  }\n\n  static async getUserSessions(userId: string) {\n    const session = await Session.aggregate([\n      {\n        $match: { userId: new mongoose.Types.ObjectId(userId), isActive: true }\n      },\n      {\n        $project: {\n          _id: 1,\n          ip: 1,\n          userAgent: 1,\n          lastUsedAt: 1,\n          expiresAt: 1,\n          isActive: 1,\n          userId: 1\n        }\n      },\n      {\n        $lookup: {\n          from: \"users\",\n          localField: \"userId\",\n          foreignField: \"_id\",\n          as: \"user\",\n          pipeline: [\n            {\n              $project: {\n                _id: 1,\n                name: 1,\n                email: 1\n              }\n            }\n          ]\n        }\n      },\n      {\n        $unwind: {\n          path: \"$user\",\n          preserveNullAndEmptyArrays: true\n        }\n      }\n    ]);\n    return session;\n  }\n\n  static async deleteUserSession(userId: string, sessionId: string) {\n    return await Session.findOneAndDelete({\n      userId,\n      _id: sessionId\n    });\n  }\n\n  static async logoutUser(userId: string, sessionId: string) {\n    return await Session.deleteMany({ userId, _id: sessionId });\n  }\n\n  static async deleteAllUserSessions(userId: string) {\n    return await Session.findOneAndDelete({\n      userId\n    });\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/helpers/token.helpers.ts",
                          "content": "import crypto from \"node:crypto\";\n\nexport function generateOTP(length: number = 6, ttlMinutes: number = 5) {\n  const code = crypto\n    .randomInt(0, Math.pow(10, length))\n    .toString()\n    .padStart(length, \"0\");\n\n  const hashCode = crypto\n    .createHash(\"sha256\")\n    .update(String(code))\n    .digest(\"hex\");\n\n  const expiresAt = new Date(Date.now() + ttlMinutes * 60 * 1000).toISOString();\n\n  return { code, hashCode, expiresAt };\n}\n\nexport function generateHashedToken(token: string): string {\n  return crypto.createHash(\"sha256\").update(String(token)).digest(\"hex\");\n}\n\nexport function generateSecureToken(length: number = 32): string {\n  return crypto.randomBytes(length).toString(\"hex\");\n}\n\nexport function verifyHashedToken(token: string, hashedToken: string): boolean {\n  return (\n    crypto.createHash(\"sha256\").update(String(token)).digest(\"hex\") ===\n    hashedToken\n  );\n}\n\nexport function generateTokenAndHashedToken(id: string) {\n  const cryptoSecret = process.env.CRYPTO_SECRET! || \"secret\";\n  const token = crypto\n    .createHmac(\"sha256\", cryptoSecret)\n    .update(String(id))\n    .digest(\"hex\");\n\n  const hashedToken = crypto\n    .createHash(\"sha256\")\n    .update(String(token))\n    .digest(\"hex\");\n  return { token, hashedToken };\n}\n\nexport function generateUUID(): string {\n  return crypto.randomUUID();\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/helpers/cookie.helper.ts",
                          "content": "import { Response } from \"express\";\nimport env from \"../configs/env\";\n\nconst isProduction = env.NODE_ENV === \"production\";\n\nexport const COOKIE_OPTIONS = {\n  httpOnly: true,\n  secure: isProduction,\n  sameSite: isProduction ? (\"none\" as const) : (\"lax\" as const),\n  path: \"/\"\n};\n\nexport function clearCookie(res: Response, cookie: string = \"sid\") {\n  res.clearCookie(cookie, COOKIE_OPTIONS);\n}\n\ntype Cookie = {\n  cookie: string;\n  value: string;\n  maxAge: number;\n};\n\nexport function setCookies(res: Response, cookies: Cookie[]) {\n  cookies.forEach(({ cookie, value, maxAge }) => {\n    res.cookie(cookie, value, {\n      ...COOKIE_OPTIONS,\n      maxAge\n    });\n  });\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/helpers/auth.helpers.ts",
                          "content": "import argon2 from \"argon2\";\n\nexport async function hashPassword(password: string): Promise<string> {\n  return argon2.hash(password);\n}\n\nexport async function verifyPassword(\n  password: string,\n  hash: string\n): Promise<boolean> {\n  return argon2.verify(hash, password);\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/docs/swagger.json",
                          "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"Statefull authentication\",\n    \"description\": \"Statefull authentication API documentation\",\n    \"version\": \"1.0.0\"\n  },\n  \"host\": \"localhost:9000/api\",\n  \"basePath\": \"/\",\n  \"schemes\": [\n    \"http\"\n  ],\n  \"paths\": {\n    \"/v1/health/\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/health/detailed\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/verify-otp\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"example\": \"any\"\n                },\n                \"otpCode\": {\n                  \"example\": \"any\"\n                },\n                \"otpType\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/signup\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"example\": \"any\"\n                },\n                \"email\": {\n                  \"example\": \"any\"\n                },\n                \"password\": {\n                  \"example\": \"any\"\n                },\n                \"role\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/signin\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"example\": \"any\"\n                },\n                \"password\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/profile\": {\n      \"get\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/sessions\": {\n      \"get\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/sessions/{sessionId}\": {\n      \"delete\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"sessionId\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/logout\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/forgot-password\": {\n      \"post\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/reset-password\": {\n      \"post\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/change-password\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/delete-account\": {\n      \"delete\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/reactivate-account\": {\n      \"put\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/github\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/github/callback\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/google\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/google/callback\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    }\n  }\n}"
                        },
                        {
                          "type": "file",
                          "path": "src/controllers/oauth.controller.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport { Profile as GithubProfile } from \"passport-github2\";\nimport { Profile as GoogleProfile } from \"passport-google-oauth20\";\n\nimport { ApiResponse } from \"../utils/api-response\";\nimport { AsyncHandler } from \"../utils/async-handler\";\nimport { ApiError } from \"../utils/api-error\";\nimport { OAuthService } from \"../services/oauth.service\";\nimport { setCookies } from \"../helpers/cookie.helper\";\nimport { SESSION_EXPIRES_IN } from \"../constants/auth\";\n\n//? LOGIN WITH GITHUB\nexport const githubOAuth = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const data = req.user as GithubProfile | undefined;\n\n    if (!data) {\n      return next(ApiError.unauthorized(\"Authenticated failed!\"));\n    }\n\n    const user = {\n      provider: data?.provider,\n      providerId: data.id,\n      name: data.displayName,\n      email: data?.emails && data?.emails[0]?.value,\n      isEmailVerified: true,\n      avatar: data.photos && data.photos[0].value\n    };\n\n    const existingUser = await OAuthService.handleOAuthLogin(user, {\n      setAuthCookie: (token: string) => {\n        setCookies(res, [\n          {\n            cookie: \"sid\",\n            value: token,\n            maxAge: SESSION_EXPIRES_IN\n          }\n        ]);\n      }\n    });\n\n    //? save the data into your databases\n\n    ApiResponse.ok(res, \"Signin Successfull\", {\n      user: {\n        _id: existingUser._id.toString(),\n        name: existingUser.name,\n        email: existingUser.email,\n        role: existingUser.role,\n        avatar: existingUser.avatar,\n        isEmailVerified: existingUser.isEmailVerified,\n        lastLoginAt: existingUser.lastLoginAt,\n        provider: existingUser.provider\n      }\n    });\n  }\n);\n\n//? LOGIN WITH GOOGLE\nexport const googleOAuth = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const data = req.user as GoogleProfile | undefined;\n\n    if (!data) {\n      return next(ApiError.unauthorized(\"Authenticated failed!\"));\n    }\n\n    const userInfo = {\n      provider: data?.provider,\n      providerId: data.id,\n      name: data.displayName,\n      email: data?.emails && data?.emails[0]?.value,\n      isEmailVerified:\n        (data?.emails && data?.emails[0]?.verified === true) || true,\n      avatar: data.profileUrl || (data.photos && data.photos[0].value)\n    };\n\n    const existingUser = await OAuthService.handleOAuthLogin(userInfo, {\n      setAuthCookie: (token: string) => {\n        setCookies(res, [\n          {\n            cookie: \"sid\",\n            value: token,\n            maxAge: SESSION_EXPIRES_IN\n          }\n        ]);\n      }\n    });\n\n    ApiResponse.ok(res, \"Signin Successfull\", {\n      user: {\n        _id: existingUser._id.toString(),\n        name: existingUser.name,\n        email: existingUser.email,\n        role: existingUser.role,\n        avatar: existingUser.avatar,\n        isEmailVerified: existingUser.isEmailVerified,\n        lastLoginAt: existingUser.lastLoginAt,\n        provider: existingUser.provider\n      }\n    });\n  }\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/controllers/health.controller.ts",
                          "content": "import { Request, Response } from \"express\";\nimport { ApiResponse } from \"../utils/api-response\";\nimport { AsyncHandler } from \"../utils/async-handler\";\n\n/**\n * Basic health check endpoint\n * GET /api/health\n */\nexport const healthCheck = AsyncHandler(\n  async (_req: Request, res: Response) => {\n    return ApiResponse.Success(res, \"Service is healthy\", {\n      status: \"healthy\",\n      timestamp: new Date().toISOString(),\n      uptime: process.uptime()\n    });\n  }\n);\n\n/**\n * Detailed health check with system information\n * GET /api/health/detailed\n */\nexport const detailedHealthCheck = AsyncHandler(\n  async (_req: Request, res: Response) => {\n    const healthData = {\n      status: \"healthy\",\n      timestamp: new Date().toISOString(),\n      uptime: process.uptime(),\n      environment: process.env.NODE_ENV || \"development\",\n      version: process.env.npm_package_version || \"1.0.0\",\n      memory: {\n        used:\n          Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) /\n          100,\n        total:\n          Math.round((process.memoryUsage().heapTotal / 1024 / 1024) * 100) /\n          100,\n        unit: \"MB\"\n      },\n      cpu: {\n        usage: process.cpuUsage()\n      }\n    };\n\n    return ApiResponse.Success(res, \"Service is healthy\", healthData);\n  }\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/controllers/auth.controller.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport { ApiResponse } from \"../utils/api-response\";\nimport { AsyncHandler } from \"../utils/async-handler\";\n\nimport { ApiError } from \"../utils/api-error\";\nimport { AuthService } from \"../services/auth.service\";\nimport { OtpService } from \"../services/otp.service\";\nimport { clearCookie, setCookies } from \"../helpers/cookie.helper\";\nimport { UserRequest } from \"../types/user\";\nimport {\n  deleteFileFromCloudinary,\n  uploadToCloudinary\n} from \"../services/cloudinary.service\";\nimport {\n  RESET_PASSWORD_TOKEN_EXPIRY,\n  SESSION_EXPIRES_IN\n} from \"../constants/auth\";\nimport { DeleteAccountType, VerifyOtpType } from \"../validators/auth\";\n\n//? VERIFY OTP\nexport const verifyOtp = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { email, otpCode, otpType }: VerifyOtpType = req.body;\n    if (!email || !otpCode || !otpType) {\n      return next(\n        ApiError.badRequest(\"Email, OTP code and OTP type are required\")\n      );\n    }\n\n    const otp = await OtpService.verifyOtp(\n      next,\n      { email, otpCode, otpType },\n      {\n        setAuthCookie: (token: string) => {\n          setCookies(res, [\n            {\n              cookie: \"sid\",\n              value: token,\n              maxAge: SESSION_EXPIRES_IN\n            }\n          ]);\n        },\n        ip: req.ip || \"unknown\",\n        userAgent: req.headers[\"user-agent\"] || \"\"\n      },\n      {\n        setCookie: (token: string) => {\n          setCookies(res, [\n            {\n              cookie: \"resetPasswordToken\",\n              value: token,\n              maxAge: RESET_PASSWORD_TOKEN_EXPIRY\n            }\n          ]);\n        }\n      }\n    );\n\n    if (!otp) {\n      return next(ApiError.server(\"Failed to verify OTP!\"));\n    }\n\n    return ApiResponse.ok(res, otp?.message || \"OTP verified successfully!\");\n  }\n);\n\n//? SIGNUP USER\nexport const signupUser = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { name, email, password, role } = req.body;\n    if (!name || !email || !password) {\n      return next(ApiError.badRequest(\"Name, email and password are required\"));\n    }\n\n    const user = await AuthService.registerUser(next, {\n      name,\n      email,\n      password,\n      role\n    });\n\n    if (!user) {\n      return next(ApiError.server(\"Failed to register user!\"));\n    }\n\n    return ApiResponse.created(res, \"User registered successfully\", {\n      name: user.name,\n      email: user.email,\n      role: user.role\n    });\n  }\n);\n\n//? SIGNIN USER\nexport const signinUser = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { email, password } = req.body;\n    if (!email || !password) {\n      return next(ApiError.badRequest(\"Email and password are required\"));\n    }\n\n    const result = await AuthService.loginAndSendOtp(next, { email, password });\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to login!\"));\n    }\n\n    return ApiResponse.ok(res, result.message || \"Otp sent successfully!\");\n  }\n);\n\n//? GET USER PROFILE\nexport const getUserProfile = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req.user?._id;\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const user = await AuthService.getUserProfile(userId.toString());\n    if (!user) {\n      return next(ApiError.notFound(\"User not found\"));\n    }\n\n    if (user.isDeleted) {\n      return next(ApiError.notFound(\"This account has been deactivated.\"));\n    }\n\n    return ApiResponse.ok(res, \"User profile fetched successfully\", {\n      user: {\n        _id: user._id,\n        name: user.name,\n        email: user.email,\n        role: user.role,\n        avatar: user.avatar,\n        isEmailVerified: user.isEmailVerified,\n        lastLoginAt: user.lastLoginAt\n      }\n    });\n  }\n);\n\n//? UPDATE PROFILE\nexport const updateProfile = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const data = req.body;\n    const { name } = data;\n\n    if (!req.user?._id) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const user = await AuthService.getUserProfile(req.user?._id.toString());\n\n    if (!user) {\n      return next(ApiError.notFound(\"User not found\"));\n    }\n\n    if (req?.file && user?.avatar?.public_id) {\n      await deleteFileFromCloudinary([user.avatar.public_id]);\n    }\n\n    if (req?.file && user?.avatar) {\n      const file = await uploadToCloudinary(req.file.buffer, {\n        folder: \"uploads/files\",\n        resource_type: \"auto\"\n      });\n      user.avatar = {\n        public_id: req.file\n          ? file.public_id\n          : (user?.avatar?.public_id as string),\n        url: req.file ? file.url : (user.avatar.url as string),\n        size: req.file ? file.size : (user.avatar.size as number)\n      };\n    }\n\n    if (name) {\n      user.name = name;\n    }\n\n    await user.save();\n\n    return ApiResponse.Success(res, \"Profile updated successfully!\", user);\n  }\n);\n\n//? LOGOUT\nexport const logoutUser = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req.user?._id;\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const currentSessionId = req.session?._id;\n    if (!currentSessionId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    await AuthService.logoutUser(userId.toString(), currentSessionId);\n\n    clearCookie(res, \"sid\");\n\n    return ApiResponse.Success(res, \"Logged out successfully!\");\n  }\n);\n\n//? FORGOT PASSWORD\nexport const forgotPassword = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { email } = req.body;\n    if (!email) {\n      return next(ApiError.badRequest(\"Email is required!\"));\n    }\n\n    const result = await AuthService.forgotPassword(next, email);\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to send otp!\"));\n    }\n\n    return ApiResponse.ok(res, result.message || \"Otp sent successfully!\");\n  }\n);\n\n//? RESET PASSWORD\nexport const resetPassword = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const { newPassword, email } = req.body;\n    if (!email || !newPassword) {\n      return next(ApiError.badRequest(\"Newpassword and email are required!\"));\n    }\n\n    const hashedResetPasswordToken = req.cookies?.hashedResetPasswordToken;\n\n    if (!hashedResetPasswordToken) {\n      return next(\n        ApiError.badRequest(\"Reset password token not found or expired\")\n      );\n    }\n\n    const result = await AuthService.resetPassword(next, email, newPassword);\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to reset password!\"));\n    }\n\n    res.clearCookie(\"hashedResetPasswordToken\");\n    return ApiResponse.ok(\n      res,\n      result.message || \"Password reset successfully!\"\n    );\n  }\n);\n\n//? CHANGE PASSWORD\nexport const changePassword = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const { oldPassword, newPassword } = req.body;\n\n    if (!oldPassword || !newPassword) {\n      return next(\n        ApiError.badRequest(\"Old password and new password are required\")\n      );\n    }\n\n    const result = await AuthService.changePassword(next, {\n      userId: userId.toString(),\n      oldPassword,\n      newPassword\n    });\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to change password!\"));\n    }\n\n    clearCookie(res, \"sid\");\n\n    return ApiResponse.ok(\n      res,\n      result.message || \"Password changed successfully!\"\n    );\n  }\n);\n\n//? DELETE/DEACTIVATE ACCOUNT\nexport const deleteAccount = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const { userId, type }: DeleteAccountType = req.body;\n\n    if (!userId || !type) {\n      return next(ApiError.badRequest(\"User id and type are required!\"));\n    }\n\n    const reqUserId = req?.user?._id;\n\n    if (!reqUserId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (userId !== reqUserId) {\n      return next(\n        ApiError.unauthorized(\"you are not authorized to perform this action\")\n      );\n    }\n\n    await AuthService.deleteOrDeactiveAccount(next, userId, type);\n\n    if (type === \"hard\") {\n      clearCookie(res, \"sid\");\n    }\n\n    return ApiResponse.Success(\n      res,\n      `Account ${type === \"soft\" ? \"deactivated\" : \"deleted\"} successfully!`\n    );\n  }\n);\n\n//? REACTIVATE ACCOUNT\nexport const reactivateAccount = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    await AuthService.reactivateAccount(next, userId);\n\n    return ApiResponse.Success(res, \"Account reactivated successfully!\");\n  }\n);\n\n//? GET USER SESSIONS\nexport const getUserSessions = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n    const currentSessionId = req.session?._id;\n\n    if (!userId || !currentSessionId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const result = await AuthService.getUserSessions(userId.toString());\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to get user sessions!\"));\n    }\n\n    return ApiResponse.ok(res, \"User sessions fetched successfully\", result);\n  }\n);\n\n//? DELETE SESSION\nexport const deleteUserSession = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n    const { sessionId } = req.params;\n\n    if (!userId || !sessionId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const result = await AuthService.deleteUserSession(\n      userId,\n      sessionId as string\n    );\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to delete user session!\"));\n    }\n\n    const reqSId = req.cookies?.sid;\n\n    const isCurrentSession = sessionId === reqSId;\n    if (isCurrentSession) {\n      clearCookie(res, \"sid\");\n    }\n\n    return ApiResponse.Success(res, \"User session deleted successfully!\");\n  }\n);\n\n//? DELETE ALL SESSIONS\nexport const deleteAllUserSessions = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const result = await AuthService.deleteAllUserSessions(userId);\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to delete user sessions!\"));\n    }\n\n    res.clearCookie(\"sid\");\n\n    return ApiResponse.Success(res, \"User sessions deleted successfully!\");\n  }\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/configs/swagger.ts",
                          "content": "import swaggerUi from \"swagger-ui-express\";\nimport { Express } from \"express\";\nimport env from \"../configs/env\";\n\nimport swaggerDocument from \"../docs/swagger.json\";\n\nexport const setupSwagger = (app: Express) => {\n  if (env.NODE_ENV !== \"development\") return;\n\n  app.use(\"/api/docs\", swaggerUi.serve, swaggerUi.setup(swaggerDocument));\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/configs/resend.ts",
                          "content": "import { Resend } from \"resend\";\nimport env from \"./env\";\n\nexport const resend = new Resend(env.RESEND_API_KEY);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/configs/passport.ts",
                          "content": "import passport from \"passport\";\nimport {\n  Strategy as GitHubStrategy,\n  Profile as GithubProfile\n} from \"passport-github2\";\n\nimport {\n  Strategy as GoogleStrategy,\n  Profile as GoogleProfile\n} from \"passport-google-oauth20\";\n\nimport env from \"./env\";\n\npassport.use(\n  new GitHubStrategy(\n    {\n      clientID: env.GITHUB_CLIENT_ID,\n      clientSecret: env.GITHUB_CLIENT_SECRET,\n      callbackURL: env.GITHUB_REDIRECT_URI\n    },\n    function (\n      accessToken: string,\n      refreshToken: string,\n      profile: GithubProfile,\n      cb: (error: Error | null, user?: any) => void\n    ) {\n      // console.log({ profile });\n      return cb(null, profile);\n    }\n  )\n);\n\npassport.use(\n  new GoogleStrategy(\n    {\n      clientID: env.GOOGLE_CLIENT_ID,\n      clientSecret: env.GOOGLE_CLIENT_SECRET,\n      callbackURL: env.GOOGLE_REDIRECT_URI\n    },\n    function (accessToken, refreshToken, profile: GoogleProfile, cb) {\n      return cb(null, profile);\n    }\n  )\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/configs/env.ts",
                          "content": "import \"dotenv-flow/config\";\nimport { z } from \"zod\";\n\nexport const envSchema = z.object({\n  NODE_ENV: z\n    .enum([\"development\", \"test\", \"production\"])\n    .default(\"development\"),\n\n  PORT: z.string().regex(/^\\d+$/, \"PORT must be a number\").transform(Number),\n\n  DATABASE_URL: z.url(),\n\n  CORS_ORIGIN: z.string(),\n\n  LOG_LEVEL: z\n    .enum([\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"])\n    .default(\"info\"),\n\n  CRYPTO_SECRET: z.string().min(32),\n\n  RESEND_API_KEY: z.string(),\n  EMAIL_FROM: z.email(),\n\n  CLOUDINARY_CLOUD_NAME: z.string(),\n  CLOUDINARY_API_KEY: z.string(),\n  CLOUDINARY_API_SECRET: z.string(),\n\n  GOOGLE_CLIENT_ID: z.string(),\n  GOOGLE_CLIENT_SECRET: z.string(),\n  GOOGLE_REDIRECT_URI: z.url(),\n\n  GITHUB_CLIENT_ID: z.string(),\n  GITHUB_CLIENT_SECRET: z.string(),\n  GITHUB_REDIRECT_URI: z.url()\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\nconst result = envSchema.safeParse(process.env);\n\nif (!result.success) {\n  console.error(\"❌ Invalid environment configuration\");\n  console.error(z.prettifyError(result.error));\n  process.exit(1);\n}\n\nexport const env: Readonly<Env> = Object.freeze(result.data);\n\nexport default env;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/configs/db.ts",
                          "content": "import mongoose from \"mongoose\";\nimport env from \"./env\";\nimport { logger } from \"../utils/logger\";\n\nexport const connectDB = async (): Promise<void> => {\n  try {\n    const conn = await mongoose.connect(env.DATABASE_URL as string);\n    logger.info(`MongoDB Connected: ${conn.connection.host}`);\n  } catch (error) {\n    logger.error(error, \"MongoDB Connection Failed\");\n    process.exit(1);\n  }\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/configs/cloudinary.ts",
                          "content": "import { v2 as cloudinary } from \"cloudinary\";\nimport env from \"./env\";\n\ncloudinary.config({\n  cloud_name: env.CLOUDINARY_CLOUD_NAME,\n  api_key: env.CLOUDINARY_API_KEY,\n  api_secret: env.CLOUDINARY_API_SECRET\n});\n\nexport default cloudinary;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/constants/status-codes.ts",
                          "content": "export const STATUS_CODES = {\n  // 2xx Success\n  OK: 200,\n  CREATED: 201,\n  ACCEPTED: 202,\n  NO_CONTENT: 204,\n\n  // 3xx Redirection\n  MOVED_PERMANENTLY: 301,\n  FOUND: 302,\n  NOT_MODIFIED: 304,\n\n  // 4xx Client Errors\n  BAD_REQUEST: 400,\n  UNAUTHORIZED: 401,\n  FORBIDDEN: 403,\n  NOT_FOUND: 404,\n  CONFLICT: 409,\n  UNPROCESSABLE_ENTITY: 422,\n  TOO_MANY_REQUESTS: 429,\n\n  // 5xx Server Errors\n  INTERNAL_SERVER_ERROR: 500,\n  NOT_IMPLEMENTED: 501,\n  BAD_GATEWAY: 502,\n  SERVICE_UNAVAILABLE: 503,\n  GATEWAY_TIMEOUT: 504\n} as const;\n\nexport type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];\n"
                        },
                        {
                          "type": "file",
                          "path": "src/constants/auth.ts",
                          "content": "export const OTP_MAX_ATTEMPTS = 5;\n\nexport const OTP_TYPES = [\n  \"signin\",\n  \"email-verification\",\n  \"password-reset\",\n  \"password-change\"\n] as const;\n\nexport const NEXT_OTP_DELAY = 1 * 60 * 1000; // 1 minute\n\nexport const LOGIN_MAX_ATTEMPTS = 5 as const;\n\nexport const OTP_CODE_LENGTH = 6 as const;\n\nexport const OTP_EXPIRES_IN = 5 * 60 * 1000; // 5 minutes\n\nexport const LOCK_TIME_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport const ACCESS_TOKEN_EXPIRY = 15 * 60 * 1000; // 15 minutes\n\nexport const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nexport const RESET_PASSWORD_TOKEN_EXPIRY = 5 * 60 * 1000; // 5 minutes\n\nexport const REACTIVATION_AVAILABLE_AT = 1 * 60 * 1000; // 24 hours\n\nexport const SESSION_EXPIRES_IN = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nexport const MAX_SESSIONS_PER_USER = 3;\n"
                        }
                      ]
                    },
                    "feature": {
                      "files": [
                        {
                          "type": "file",
                          "path": "swagger.config.ts",
                          "content": "import swaggerAutoGen from \"swagger-autogen\";\n\nconst doc = {\n  info: {\n    title: \"Stateful authentication\",\n    description: \"Stateful authentication API documentation\",\n    version: \"1.0.0\"\n  },\n  host: \"localhost:9000\",\n  schemes: [\"http\"]\n};\n\nconst outputFile = \"./src/docs/swagger.json\"; // Output file for the generated docs\nconst endpointsFiles = [\"./src/routes/*.ts\"]; // Endpoints files to be parsed\n\nswaggerAutoGen(outputFile, endpointsFiles, doc);\n"
                        },
                        {
                          "type": "file",
                          "path": "package.json",
                          "content": "{\n  \"name\": \"my-express-app\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/server.js\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"cross-env NODE_ENV=development npx tsx watch src/server.ts\",\n    \"build\": \"rm -rf dist && tsc && tsc-alias\",\n    \"start\": \"cross-env NODE_ENV=production node  dist/server.js\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"docs\": \"npx tsx swagger.config.ts\",\n    \"format:check\": \"npx prettier . --check\",\n    \"format:fix\": \"npx prettier . --write\",\n    \"lint:check\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"prepare\": \"husky\"\n  },\n  \"lint-staged\": {\n    \"src/**/*.ts\": [\n      \"eslint --fix\",\n      \"prettier --write\"\n    ],\n    \"*.json\": [\n      \"prettier --write\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^20.3.1\",\n    \"@commitlint/config-conventional\": \"^20.3.1\",\n    \"@types/cookie-parser\": \"^1.4.10\",\n    \"@types/cors\": \"^2.8.19\",\n    \"@types/express\": \"^5.0.6\",\n    \"@types/helmet\": \"^0.0.48\",\n    \"@types/morgan\": \"^1.9.10\",\n    \"@types/multer\": \"^2.0.0\",\n    \"@types/node\": \"^24.10.0\",\n    \"@types/nodemailer\": \"^7.0.5\",\n    \"@types/passport\": \"^1.0.17\",\n    \"@types/passport-github2\": \"^1.2.9\",\n    \"@types/passport-google-oauth20\": \"^2.0.17\",\n    \"@types/swagger-ui-express\": \"^4.1.8\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.57.0\",\n    \"@typescript-eslint/parser\": \"^8.57.0\",\n    \"eslint\": \"^10.0.3\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"husky\": \"^9.1.7\",\n    \"morgan\": \"^1.10.1\",\n    \"prettier\": \"^3.8.1\",\n    \"tsc-alias\": \"^1.8.16\",\n    \"tsx\": \"^4.21.0\",\n    \"typescript\": \"^5.9.3\"\n  },\n  \"dependencies\": {\n    \"argon2\": \"^0.44.0\",\n    \"cloudinary\": \"^2.9.0\",\n    \"cookie-parser\": \"^1.4.7\",\n    \"cors\": \"^2.8.5\",\n    \"cross-env\": \"^10.1.0\",\n    \"dotenv-flow\": \"^4.1.0\",\n    \"express\": \"^5.2.1\",\n    \"express-rate-limit\": \"^8.2.1\",\n    \"helmet\": \"^8.1.0\",\n    \"mongoose\": \"^9.1.4\",\n    \"multer\": \"^2.0.2\",\n    \"nodemailer\": \"^7.0.12\",\n    \"passport\": \"^0.7.0\",\n    \"passport-github2\": \"^0.1.12\",\n    \"passport-google-oauth20\": \"^2.0.0\",\n    \"pino\": \"^10.2.0\",\n    \"pino-pretty\": \"^13.1.3\",\n    \"resend\": \"^6.9.3\",\n    \"swagger-autogen\": \"^2.23.7\",\n    \"swagger-ui-express\": \"^5.0.1\",\n    \"zod\": \"^4.3.5\"\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/server.ts",
                          "content": "import app from \"./app\";\nimport { connectDB } from \"./shared/configs/db\";\nimport env from \"./shared/configs/env\";\nimport { logger } from \"./shared/utils/logger\";\nimport { configureGracefulShutdown } from \"./shared/utils/shutdown\";\n\nconst port = env.PORT || 9000;\n\nconnectDB();\n\nconst server = app.listen(port, () => {\n  logger.info(`[server]: Server is running at http://localhost:${port}`);\n  logger.info(`[server]: Environment: ${port}`);\n  logger.info(`[server]: Swagger Docs: http://localhost:${port}/api/docs`);\n});\n\nconfigureGracefulShutdown(server);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/app.ts",
                          "content": "import express, { Express, Request, Response } from \"express\";\nimport cookieParser from \"cookie-parser\";\nimport morgan from \"morgan\";\nimport { errorHandler } from \"./shared/middlewares/error-handler\";\nimport env from \"./shared/configs/env\";\n\nimport Routes from \"./routes/index\";\n\nimport \"./shared/configs/passport\";\nimport { notFoundHandler } from \"./shared/middlewares/not-found-handler\";\nimport { configureSecurityHeaders } from \"./shared/middlewares/security-header\";\nimport { setupSwagger } from \"./shared/configs/swagger\";\n\nconst app: Express = express();\n\n//? Apply security headers before other middlewares and routes\nconfigureSecurityHeaders(app);\n\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(cookieParser());\napp.use(morgan(env.NODE_ENV === \"development\" ? \"dev\" : \"combined\"));\n\n//? Initialize Swagger\nsetupSwagger(app);\n\n//? Routes\napp.get(\"/\", (req: Request, res: Response) => {\n  res.redirect(\"/api/v1/health\");\n});\n\napp.use(\"/api\", Routes);\n\n//? Not-found-handler (should be after routes)\napp.use(notFoundHandler);\n\n//? Global error handler (should be last)\napp.use(errorHandler);\n\nexport default app;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/types/global.d.ts",
                          "content": "import { Request } from \"express\";\n\nexport interface UserRequest extends Request {\n  user?: {\n    _id?: string;\n    role?: \"user\" | \"admin\" | undefined;\n  };\n  session?: {\n    _id?: string;\n    token?: string;\n  };\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/routes/index.ts",
                          "content": "import { Router } from \"express\";\nimport healthRoutes from \"../modules/health/health.routes\";\nimport authRoutes from \"../modules/auth/auth.routes\";\nimport oauthRoutes from \"../modules/oauth/oauth.routes\";\n\nconst router = Router();\n\nrouter.use(\"/v1/health\", healthRoutes);\nrouter.use(\"/v1/auth\", authRoutes);\nrouter.use(\"/auth\", oauthRoutes); //* Here versioning is not given because, in google and github callback routes, we are not using versioning. process.env.GOOGLE_REDIRECT_URI\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/routes/health.routes.ts",
                          "content": "import { Router } from \"express\";\nconst router = Router();\n\nrouter.get(\"/v1/health/\", (req, res) => {\n  res.json({ message: \"Server is running\" });\n});\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": ".husky/pre-commit",
                          "content": "npx lint-staged\n"
                        },
                        {
                          "type": "file",
                          "path": "src/docs/swagger.json",
                          "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"Statefull authentication\",\n    \"description\": \"Statefull authentication API documentation\",\n    \"version\": \"1.0.0\"\n  },\n  \"host\": \"localhost:9000/api\",\n  \"basePath\": \"/\",\n  \"schemes\": [\"http\"],\n  \"paths\": {\n    \"/v1/health/\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/health/detailed\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/verify-otp\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"example\": \"any\"\n                },\n                \"otpCode\": {\n                  \"example\": \"any\"\n                },\n                \"otpType\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/signup\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"example\": \"any\"\n                },\n                \"email\": {\n                  \"example\": \"any\"\n                },\n                \"password\": {\n                  \"example\": \"any\"\n                },\n                \"role\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/signin\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"example\": \"any\"\n                },\n                \"password\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/profile\": {\n      \"get\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/sessions\": {\n      \"get\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/sessions/{sessionId}\": {\n      \"delete\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"sessionId\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/logout\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"example\": \"any\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/forgot-password\": {\n      \"post\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/reset-password\": {\n      \"post\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/change-password\": {\n      \"post\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/delete-account\": {\n      \"delete\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/v1/auth/reactivate-account\": {\n      \"put\": {\n        \"description\": \"\",\n        \"parameters\": [\n          {\n            \"name\": \"authorization\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"user-agent\",\n            \"in\": \"header\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/github\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/github/callback\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/google\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    },\n    \"/auth/google/callback\": {\n      \"get\": {\n        \"description\": \"\",\n        \"responses\": {\n          \"default\": {\n            \"description\": \"\"\n          }\n        }\n      }\n    }\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/utils/shutdown.ts",
                          "content": "import { Server } from \"http\";\nimport { logger } from \"./logger\";\n\nexport const configureGracefulShutdown = (server: Server) => {\n  const signals = [\"SIGTERM\", \"SIGINT\"];\n\n  signals.forEach(signal => {\n    process.on(signal, () => {\n      logger.info(`\\n${signal} signal received. Shutting down gracefully...`);\n\n      server.close(err => {\n        if (err) {\n          logger.error(err, \"Error during server close\");\n          process.exit(1);\n        }\n\n        logger.info(\"HTTP server closed.\");\n        process.exit(0);\n      });\n\n      // Force shutdown after 10 seconds\n      setTimeout(() => {\n        logger.error(\n          \"Could not close connections in time, forcefully shutting down\"\n        );\n        process.exit(1);\n      }, 10000);\n    });\n  });\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/utils/send-mail.ts",
                          "content": "import env from \"../configs/env\";\nimport { resend } from \"../configs/resend\";\n\nexport type SendMail = {\n  from?: string;\n  subject: string;\n  data: Record<string, any>;\n  email: string;\n  html: string;\n};\n\nexport async function sendEmail({ from, email, subject, html }: SendMail) {\n  return await resend.emails.send({\n    from: from || `<${env.EMAIL_FROM}>`,\n    to: email,\n    subject,\n    replyTo: email,\n    html\n  });\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/utils/logger.ts",
                          "content": "import pino from \"pino\";\nimport env from \"../configs/env\";\n\nexport const logger = pino({\n  level: env.LOG_LEVEL,\n  transport:\n    env.NODE_ENV !== \"production\"\n      ? {\n          target: \"pino-pretty\",\n          options: {\n            colorize: true,\n            translateTime: \"yyyy-mm-dd HH:MM:ss\",\n            ignore: \"pid,hostname\"\n          }\n        }\n      : undefined\n});\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/utils/async-handler.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\n\nexport type AsyncRouteHandler = (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => Promise<unknown>;\n\nexport function AsyncHandler(fn: AsyncRouteHandler) {\n  return function (req: Request, res: Response, next: NextFunction) {\n    Promise.resolve()\n      .then(() => fn(req, res, next))\n      .catch(next);\n  };\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/utils/api-response.ts",
                          "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\nimport type { Response } from \"express\";\n\ntype ApiResponseParams<T> = {\n  success: boolean;\n  message: string;\n  statusCode: StatusCode;\n  data?: T | null;\n  errors?: unknown;\n};\n\nexport class ApiResponse<T = unknown> {\n  public readonly success: boolean;\n  public readonly message: string;\n  public readonly statusCode: StatusCode;\n  public readonly data?: T | null;\n  public readonly errors?: unknown;\n\n  constructor({\n    success,\n    message,\n    statusCode,\n    data,\n    errors\n  }: ApiResponseParams<T>) {\n    this.success = success;\n    this.message = message;\n    this.statusCode = statusCode;\n    this.data = data;\n    this.errors = errors;\n  }\n\n  send(res: Response): Response {\n    return res.status(this.statusCode).json({\n      success: this.success,\n      message: this.message,\n      statusCode: this.statusCode,\n      ...(this.data !== undefined && { data: this.data }),\n      ...(this.errors !== undefined && { errors: this.errors })\n    });\n  }\n\n  static Success<T>(\n    res: Response,\n    message: string,\n    data?: T,\n    statusCode: StatusCode = STATUS_CODES.OK\n  ): Response {\n    return new ApiResponse<T>({\n      success: true,\n      message,\n      data,\n      statusCode\n    }).send(res);\n  }\n\n  static ok<T>(res: Response, message = \"OK\", data?: T) {\n    return ApiResponse.Success(res, message, data, STATUS_CODES.OK);\n  }\n\n  static created<T>(res: Response, message = \"Created\", data?: T) {\n    return ApiResponse.Success(res, message, data, STATUS_CODES.CREATED);\n  }\n}\n\n/*\n * Usage:\n * ApiResponse.ok(res, \"OK\", data);\n * ApiResponse.created(res, \"Created\", data);\n */\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/utils/api-error.ts",
                          "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\n\nexport class ApiError extends Error {\n  public readonly statusCode: StatusCode;\n  public readonly isOperational: boolean;\n  public readonly errors?: unknown;\n\n  constructor(\n    statusCode: StatusCode,\n    message: string,\n    errors?: unknown,\n    isOperational = true\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n    this.statusCode = statusCode;\n    this.errors = errors;\n    this.isOperational = isOperational;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n\n  static badRequest(message = \"Bad Request\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static unauthorized(message = \"Unauthorized\") {\n    return new ApiError(STATUS_CODES.UNAUTHORIZED, message);\n  }\n\n  static forbidden(message = \"Forbidden\") {\n    return new ApiError(STATUS_CODES.FORBIDDEN, message);\n  }\n\n  static notFound(message = \"Not Found\") {\n    return new ApiError(STATUS_CODES.NOT_FOUND, message);\n  }\n\n  static conflict(message = \"Conflict\") {\n    return new ApiError(STATUS_CODES.CONFLICT, message);\n  }\n\n  static server(message = \"Internal Server Error\") {\n    return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);\n  }\n\n  static unprocessableEntity(message = \"Unprocessable Entity\") {\n    return new ApiError(STATUS_CODES.UNPROCESSABLE_ENTITY, message);\n  }\n\n  static tooManyRequests(message = \"Too Many Requests\") {\n    return new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, message);\n  }\n}\n\n/*\n  ? Usage:\n  * throw new ApiError(404, \"Not found\");\n  * throw ApiError.badRequest(\"Bad request\");\n */\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/constants/status-codes.ts",
                          "content": "export const STATUS_CODES = {\n  // 2xx Success\n  OK: 200,\n  CREATED: 201,\n  ACCEPTED: 202,\n  NO_CONTENT: 204,\n\n  // 3xx Redirection\n  MOVED_PERMANENTLY: 301,\n  FOUND: 302,\n  NOT_MODIFIED: 304,\n\n  // 4xx Client Errors\n  BAD_REQUEST: 400,\n  UNAUTHORIZED: 401,\n  FORBIDDEN: 403,\n  NOT_FOUND: 404,\n  CONFLICT: 409,\n  UNPROCESSABLE_ENTITY: 422,\n  TOO_MANY_REQUESTS: 429,\n\n  // 5xx Server Errors\n  INTERNAL_SERVER_ERROR: 500,\n  NOT_IMPLEMENTED: 501,\n  BAD_GATEWAY: 502,\n  SERVICE_UNAVAILABLE: 503,\n  GATEWAY_TIMEOUT: 504\n} as const;\n\nexport type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/upload/upload.service.ts",
                          "content": "import { DeleteApiResponse } from \"cloudinary\";\nimport cloudinary from \"../../shared/configs/cloudinary\";\n\nexport interface UploadOptions {\n  folder: string;\n  resource_type?: \"image\" | \"video\" | \"raw\" | \"auto\";\n}\n\nexport interface CloudinaryUploadResult {\n  url: string;\n  public_id: string;\n  size: number;\n}\n\nexport const uploadToCloudinary = (\n  buffer: Buffer,\n  options: UploadOptions\n): Promise<CloudinaryUploadResult> => {\n  return new Promise((resolve, reject) => {\n    const stream = cloudinary.uploader.upload_stream(\n      {\n        folder: options.folder || \"uploads\",\n        resource_type: options.resource_type || \"auto\"\n      },\n      (error, result) => {\n        if (error || !result) {\n          return reject(error);\n        }\n        resolve({\n          url: result.secure_url,\n          public_id: result.public_id,\n          size: result.bytes\n        });\n      }\n    );\n\n    stream.end(buffer);\n  });\n};\n\nexport const deleteFileFromCloudinary = (\n  publicIds: string[]\n): Promise<DeleteApiResponse> => {\n  return new Promise((resolve, reject) => {\n    cloudinary.api.delete_resources(publicIds, (error, result) => {\n      if (error || !result) {\n        return reject(error);\n      }\n      resolve(result);\n    });\n  });\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/verify-auth.ts",
                          "content": "import { NextFunction, Response } from \"express\";\n\nimport { ApiError } from \"../utils/api-error\";\nimport { UserRequest } from \"@/types/global\";\nimport { generateHashedToken } from \"../helpers/token.helpers\";\nimport Session from \"@/modules/auth/session.model\";\nimport User from \"@/modules/auth/user.model\";\nimport { SESSION_EXPIRES_IN } from \"@/modules/auth/auth.constants\";\nimport { logger } from \"../utils/logger\";\n\nexport async function verifyAuthentication(\n  req: UserRequest,\n  res: Response,\n  next: NextFunction\n): Promise<void> {\n  try {\n    const sid = req.cookies?.sid;\n    const authorization = req.headers.authorization;\n    const token = authorization?.split(\" \")[1];\n\n    if (!sid && !token) {\n      return next(ApiError.unauthorized(\"Unauthorized, please login.\"));\n    }\n\n    const hashedSession = generateHashedToken(sid || token);\n\n    const session = await Session.findOne({\n      tokenHash: hashedSession,\n      isActive: true,\n      expiresAt: { $gt: new Date() }\n    });\n\n    if (!session) {\n      return next(ApiError.unauthorized(\"Session expired.\"));\n    }\n\n    if (\n      session.userAgent !== req.headers[\"user-agent\"] ||\n      session.ip !== req.ip\n    ) {\n      return next(ApiError.unauthorized(\"Invalid session.\"));\n    }\n\n    const user = await User.findById(session.userId);\n\n    if (!user) {\n      return next(ApiError.unauthorized(\"User not found.\"));\n    }\n    req.user = {\n      _id: user._id.toString(),\n      role: user.role\n    };\n\n    req.session = {\n      _id: session._id.toString(),\n      token: sid || token\n    };\n\n    const remainingTime = session.expiresAt.getTime() - Date.now();\n    const EXTEND_THRESHOLD = SESSION_EXPIRES_IN * 0.25;\n\n    if (remainingTime < EXTEND_THRESHOLD) {\n      await Session.updateOne(\n        { _id: session._id.toString() },\n        {\n          $set: {\n            lastUsedAt: new Date(),\n            expiresAt: new Date(Date.now() + SESSION_EXPIRES_IN)\n          }\n        }\n      );\n    }\n\n    return next();\n  } catch (error) {\n    logger.error(error, \"Authentication verification failed\");\n    return next(\n      ApiError.server(\"Internal server error during authentication.\")\n    );\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/validate-request.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\nimport z, { ZodError, type ZodObject } from \"zod\";\n\nimport { ApiError } from \"../utils/api-error\";\n\nexport const validateRequest = (schema: ZodObject<any>) => {\n  return (req: Request, res: Response, next: NextFunction) => {\n    try {\n      schema.parse(req.body);\n\n      next();\n    } catch (error) {\n      if (!(error instanceof ZodError)) {\n        return next(error);\n      }\n\n      return next(\n        ApiError.badRequest(\n          \"Invalid request data\",\n          z.flattenError(error).fieldErrors || z.flattenError(error)\n        )\n      );\n    }\n  };\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/validate-id.ts",
                          "content": "import { isValidObjectId } from \"mongoose\";\nimport { ApiError } from \"../utils/api-error\";\nimport { NextFunction, Request, Response } from \"express\";\n\nexport const validateObjectId = (paramName: string = \"id\") => {\n  return (req: Request, res: Response, next: NextFunction) => {\n    const value =\n      req?.params[paramName] || req?.body[paramName] || req?.query[paramName];\n    if (!value) {\n      throw ApiError.badRequest(`${paramName} is required`);\n    }\n\n    if (!isValidObjectId(value)) {\n      throw ApiError.badRequest(`Invalid ${paramName}`);\n    }\n\n    next();\n  };\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/user-account-restriction.ts",
                          "content": "import { NextFunction, Response } from \"express\";\nimport { ApiError } from \"../utils/api-error\";\nimport { logger } from \"../utils/logger\";\nimport { UserRequest } from \"@/types/global\";\nimport User from \"@/modules/auth/user.model\";\n\nexport async function checkUserAccountRestriction(\n  req: UserRequest,\n  _res: Response,\n  next: NextFunction\n): Promise<void> {\n  try {\n    if (!req.user?._id) {\n      return next(ApiError.unauthorized(\"Unauthorized\"));\n    }\n\n    const user = await User.findById(req.user._id);\n\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized, please login.\"));\n    }\n\n    if (user.isDeleted || user.deletedAt) {\n      return next(ApiError.forbidden(\"Your account has been deactivated.\"));\n    }\n\n    if (user.lockUntil && user.lockUntil.getTime() > Date.now()) {\n      const minutesLeft = Math.ceil(\n        (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n      );\n\n      return next(\n        ApiError.forbidden(\n          `Your account has been locked. Please try again after ${minutesLeft} minutes.`\n        )\n      );\n    }\n\n    if (!user.isEmailVerified) {\n      return next(\n        ApiError.forbidden(\"Email not verified. Please verify your email.\")\n      );\n    }\n\n    return next();\n  } catch (err: any) {\n    logger.error(err?.message || err);\n    return next(ApiError.server(\"Something went wrong\"));\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/upload-file.ts",
                          "content": "import multer from \"multer\";\n\nexport const ALLOWED_FILE_TYPES = [\n  \"image/jpeg\",\n  \"image/png\",\n  \"image/webp\",\n  \"video/mp4\",\n  \"video/mpeg\",\n  \"video/quicktime\",\n  \"application/pdf\"\n];\n\nexport const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB\n\nconst storage = multer.memoryStorage();\n\nconst fileFilter: multer.Options[\"fileFilter\"] = (_req, file, cb) => {\n  if (!ALLOWED_FILE_TYPES.includes(file.mimetype)) {\n    return cb(null, false);\n  }\n  cb(null, true);\n};\n\nconst upload = multer({\n  storage,\n  limits: { fileSize: MAX_FILE_SIZE },\n  fileFilter\n});\n\nexport default upload;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/security-header.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport cors from \"cors\";\nimport { Express } from \"express\";\nimport helmet from \"helmet\";\nimport env from \"../configs/env\";\n\nexport const configureSecurityHeaders = (app: Express) => {\n  // Use Helmet to set various security-related HTTP headers\n  app.use(helmet());\n\n  // Configure CORS\n  app.use(\n    cors({\n      origin: env.CORS_ORIGIN || \"*\",\n      credentials: true,\n      methods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"],\n      allowedHeaders: [\"Content-Type\", \"Authorization\", \"X-Requested-With\"]\n    })\n  );\n\n  // Additional custom security headers\n  app.use((req: Request, res: Response, next: NextFunction) => {\n    res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n    res.setHeader(\"X-Frame-Options\", \"DENY\");\n    res.setHeader(\"X-XSS-Protection\", \"1; mode=block\");\n    next();\n  });\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/rate-limiter.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport { rateLimit } from \"express-rate-limit\";\nimport { STATUS_CODES } from \"../constants/status-codes\";\nimport { ApiError } from \"../utils/api-error\";\n\nexport const rateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // Limit each IP to 100 requests per window\n  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers\n  legacyHeaders: false, // Disable the `X-RateLimit-*` headers\n  message: {\n    success: false,\n    message:\n      \"Too many requests from this IP, please try again after 15 minutes\",\n    status: 429\n  },\n  handler: (req: Request, res: Response, next: NextFunction, options: any) => {\n    next(new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, options.message.message));\n  }\n});\n\n/**\n * Stricter rate limiter for sensitive routes (e.g., auth, login)\n */\nexport const authRateLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1 hour\n  max: 5, // Limit each IP to 5 failed attempts per hour\n  handler: (req, res, next, options) => {\n    next(\n      ApiError.tooManyRequests(\n        \"Too many login attempts, please try again after an hour\"\n      )\n    );\n  }\n});\n\n/**\n * Rate limiter for login route\n */\nexport const signinRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many login attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\n/**\n * Rate limiter for registration route\n */\nexport const signupRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many registration attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpRequestLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP requests. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpVerificationLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP verification attempts. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const resetPasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many password reset attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const deleteAccountLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many account deletion attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const changePasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many password change attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/not-found-handler.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\nimport { ApiError } from \"../utils/api-error\";\n\nexport const notFoundHandler = (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  throw ApiError.notFound(`Route ${req.method} ${req.originalUrl} not found`);\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/middlewares/error-handler.ts",
                          "content": "import { Request, Response, NextFunction } from \"express\";\nimport env from \"../configs/env\";\n\nimport { logger } from \"../utils/logger\";\nimport { ApiError } from \"../utils/api-error\";\n\nexport const errorHandler = (\n  err: Error,\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  if (res.headersSent) {\n    return next(err);\n  }\n  let statusCode = 500;\n  let message = \"Internal server error\";\n  let errors: unknown;\n\n  if (err instanceof ApiError) {\n    statusCode = err.statusCode;\n    message = err.message;\n    errors = err.errors;\n  }\n\n  logger.error(\n    err,\n    `Error: ${message} | Status: ${statusCode} | Path: ${req.method} ${req.originalUrl}`\n  );\n\n  const response = {\n    success: false,\n    message,\n    statusCode,\n    ...(errors !== undefined && { errors }),\n    ...(env.NODE_ENV === \"development\" && { stack: err.stack })\n  };\n\n  res.status(statusCode).json(response);\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/otp/otp.service.ts",
                          "content": "import { NextFunction } from \"express\";\nimport {\n  NEXT_OTP_DELAY,\n  OTP_CODE_LENGTH,\n  OTP_EXPIRES_IN,\n  OTP_MAX_ATTEMPTS,\n  RESET_PASSWORD_TOKEN_EXPIRY\n} from \"../auth/auth.constants\";\nimport { AuthService } from \"../auth/auth.service\";\nimport env from \"../../shared/configs/env\";\nimport { IUser, OTPType } from \"../auth/auth.types\";\nimport User from \"../auth/user.model\";\nimport Otp from \"./otp.model\";\nimport {\n  generateHashedToken,\n  generateOTP\n} from \"../../shared/helpers/token.helpers\";\nimport { SendMail, sendEmail } from \"../../shared/utils/send-mail\";\nimport { logger } from \"../../shared/utils/logger\";\nimport { ApiError } from \"../../shared/utils/api-error\";\n\ntype VerifyOtpPayload = {\n  email: string;\n  otpCode: string;\n  otpType: OTPType;\n};\n\ntype VerifyOtpContext = {\n  setAuthCookie?: (token: string) => void;\n  ip?: string;\n  userAgent?: string;\n};\n\ntype ResetPassowrdContext = {\n  setCookie?: (token: string) => void;\n};\n\ntype SendOtpPayload = {\n  email: string;\n  otpType: OTPType;\n  subject: string;\n};\n\nexport class OtpService {\n  static async sendOtp(next: NextFunction, payload: SendOtpPayload) {\n    const { email, otpType, subject } = payload;\n\n    const user = await User.findOne({ email });\n    if (!user) {\n      return next(ApiError.badRequest(\"Invalid request\"));\n    }\n\n    if (user.lockUntil && user.lockUntil > new Date()) {\n      return next(ApiError.badRequest(\"Account locked\"));\n    }\n\n    const existingOtp = await Otp.findOne({ email, type: otpType });\n\n    if (existingOtp && existingOtp.nextResendAllowedAt > new Date()) {\n      const remainingSec = Math.ceil(\n        (existingOtp.nextResendAllowedAt.getTime() - Date.now()) / 1000\n      );\n      return next(\n        ApiError.badRequest(\n          `Please wait ${remainingSec} seconds before requesting another OTP`\n        )\n      );\n    }\n\n    const otp = generateOTP(OTP_CODE_LENGTH, OTP_EXPIRES_IN);\n    logger.info(\n      `Sending OTP to ${email} with type ${otpType} and code ${otp.code}`\n    );\n    const nextResendAllowedAt = new Date(Date.now() + NEXT_OTP_DELAY);\n\n    let html = `<p>Your OTP for ${otpType}: ${otp.code}</p>`;\n    await sendEmail({\n      from: env.EMAIL_FROM,\n      email,\n      subject: subject,\n      html\n    } as SendMail);\n\n    await Otp.create({\n      email: payload.email,\n      type: payload.otpType,\n      otpHashCode: otp.hashCode,\n      attempts: 0,\n      isUsed: false,\n      expiresAt: otp.expiresAt,\n      nextResendAllowedAt\n    });\n\n    return { message: `OTP sent to ${email} successfully` };\n  }\n\n  static async verifyOtp(\n    next: NextFunction,\n    payload: VerifyOtpPayload,\n    context: VerifyOtpContext,\n    resetPasswordContext: ResetPassowrdContext\n  ): Promise<any> {\n    const { email, otpCode, otpType } = payload;\n\n    const user = await User.findOne({ email });\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized, Please login first.\"));\n    }\n\n    if (user.lockUntil && user.lockUntil > new Date()) {\n      const minutes = Math.ceil(\n        (user.lockUntil.getTime() - Date.now()) / 60000\n      );\n      return next(\n        ApiError.badRequest(\n          `Your account has been locked. Try again in ${minutes} minutes.`\n        )\n      );\n    }\n\n    const otp = await Otp.findOne({\n      email,\n      type: otpType,\n      isUsed: false,\n      expiresAt: { $gt: new Date() }\n    })\n      .sort({ createdAt: -1 })\n      .select(\"+otpHashCode\");\n\n    if (!otp) {\n      return next(ApiError.badRequest(\"Invalid or expired OTP\"));\n    }\n\n    if (otp.attempts >= (otp.maxAttempts || OTP_MAX_ATTEMPTS)) {\n      return next(ApiError.badRequest(\"Maximum OTP attempts reached\"));\n    }\n\n    const hashedOtp = generateHashedToken(String(otpCode));\n\n    if (otp.otpHashCode !== hashedOtp) {\n      await Otp.updateOne({ _id: otp._id }, { $inc: { attempts: 1 } });\n      return next(ApiError.badRequest(\"Invalid OTP code\"));\n    }\n\n    otp.isUsed = true;\n    await otp.save();\n\n    await Otp.deleteOne({ _id: otp._id });\n    await Otp.deleteMany({\n      $or: [{ expiresAt: { $lt: new Date() }, isUsed: true }]\n    });\n    if (otp.type === \"signin\") {\n      return await AuthService.handleToken(\n        {\n          _id: user._id.toString(),\n          role: user.role,\n          isEmailVerified: user.isEmailVerified,\n          email\n        },\n        context\n      );\n    }\n\n    if (otp.type === \"password-reset\") {\n      return this.handlePasswordReset(\n        {\n          _id: user._id.toString()\n        },\n        resetPasswordContext\n      );\n    }\n\n    return { message: \"OTP verified successfully\" };\n  }\n\n  private static handlePasswordReset(\n    user: Pick<IUser, \"_id\">,\n    context: ResetPassowrdContext\n  ) {\n    const hashedResetPasswordToken = generateHashedToken(user._id.toString());\n    const resetPasswordExpiry = new Date(\n      Date.now() + RESET_PASSWORD_TOKEN_EXPIRY\n    );\n\n    context.setCookie && context.setCookie(hashedResetPasswordToken);\n\n    return {\n      hashedResetPasswordToken,\n      resetPasswordExpiry\n    };\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/otp/otp.model.ts",
                          "content": "import mongoose, { Document, Model, Schema } from \"mongoose\";\nimport { OTPType } from \"../auth/auth.types\";\nimport {\n  OTP_EXPIRES_IN,\n  OTP_MAX_ATTEMPTS,\n  OTP_TYPES\n} from \"../auth/auth.constants\";\n\n//? otp interface\nexport interface IOtp extends Document {\n  _id: mongoose.Types.ObjectId;\n  email: string;\n  otpHashCode: string;\n  nextResendAllowedAt: Date;\n  type: OTPType;\n  expiresAt: Date;\n  isUsed: boolean;\n  usedAt?: Date;\n  attempts: number;\n  maxAttempts: number;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\n//? otp schema\nconst otpSchema = new Schema<IOtp>(\n  {\n    email: {\n      type: String,\n      required: [true, \"Email is required\"],\n      lowercase: true,\n      trim: true\n    },\n    otpHashCode: {\n      type: String,\n      required: [true, \"OTP hash code is required\"],\n      select: false // Never return OTP hash code in queries by default\n    },\n    nextResendAllowedAt: {\n      type: Date,\n      required: [true, \"Next resend allowed at is required\"]\n    },\n    type: {\n      type: String,\n      enum: OTP_TYPES,\n      required: [true, \"OTP type is required\"]\n    },\n    expiresAt: {\n      type: Date,\n      required: [true, \"Expiration time is required\"]\n    },\n    isUsed: {\n      type: Boolean,\n      default: false\n    },\n    usedAt: {\n      type: Date\n    },\n    attempts: {\n      type: Number,\n      default: 0\n    },\n    maxAttempts: {\n      type: Number,\n      default: OTP_MAX_ATTEMPTS // Prevent brute force attacks\n    }\n  },\n  {\n    timestamps: true\n  }\n);\n\n// Performance Indexes\notpSchema.index({ email: 1, type: 1 }); // Quick lookup by email and type\n\notpSchema.index(\n  { createdAt: 1 },\n  { expireAfterSeconds: OTP_EXPIRES_IN / 1000 } // 5 minutes\n); // ttl index\n\nconst Otp: Model<IOtp> =\n  mongoose.models.Otp || mongoose.model<IOtp>(\"Otp\", otpSchema);\n\nexport default Otp;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/helpers/token.helpers.ts",
                          "content": "import crypto from \"node:crypto\";\n\nexport function generateOTP(length: number = 6, ttlMinutes: number = 5) {\n  const code = crypto\n    .randomInt(0, Math.pow(10, length))\n    .toString()\n    .padStart(length, \"0\");\n\n  const hashCode = crypto\n    .createHash(\"sha256\")\n    .update(String(code))\n    .digest(\"hex\");\n\n  const expiresAt = new Date(Date.now() + ttlMinutes * 60 * 1000).toISOString();\n\n  return { code, hashCode, expiresAt };\n}\n\nexport function generateHashedToken(token: string): string {\n  return crypto.createHash(\"sha256\").update(String(token)).digest(\"hex\");\n}\n\nexport function generateSecureToken(length: number = 32): string {\n  return crypto.randomBytes(length).toString(\"hex\");\n}\n\nexport function verifyHashedToken(token: string, hashedToken: string): boolean {\n  return (\n    crypto.createHash(\"sha256\").update(String(token)).digest(\"hex\") ===\n    hashedToken\n  );\n}\n\nexport function generateTokenAndHashedToken(id: string) {\n  const cryptoSecret = process.env.CRYPTO_SECRET! || \"secret\";\n  const token = crypto\n    .createHmac(\"sha256\", cryptoSecret)\n    .update(String(id))\n    .digest(\"hex\");\n\n  const hashedToken = crypto\n    .createHash(\"sha256\")\n    .update(String(token))\n    .digest(\"hex\");\n  return { token, hashedToken };\n}\n\nexport function generateUUID(): string {\n  return crypto.randomUUID();\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/helpers/cookie.helper.ts",
                          "content": "import { Response } from \"express\";\nimport env from \"../configs/env\";\n\nconst isProduction = env.NODE_ENV === \"production\";\n\nexport const COOKIE_OPTIONS = {\n  httpOnly: true,\n  secure: isProduction,\n  sameSite: isProduction ? (\"none\" as const) : (\"lax\" as const),\n  path: \"/\"\n};\n\nexport function clearCookie(res: Response, cookie: string = \"sid\") {\n  res.clearCookie(cookie, COOKIE_OPTIONS);\n}\n\ntype Cookie = {\n  cookie: string;\n  value: string;\n  maxAge: number;\n};\n\nexport function setCookies(res: Response, cookies: Cookie[]) {\n  cookies.forEach(({ cookie, value, maxAge }) => {\n    res.cookie(cookie, value, {\n      ...COOKIE_OPTIONS,\n      maxAge\n    });\n  });\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/helpers/auth.helpers.ts",
                          "content": "import argon2 from \"argon2\";\n\nexport async function hashPassword(password: string): Promise<string> {\n  return argon2.hash(password);\n}\n\nexport async function verifyPassword(\n  password: string,\n  hash: string\n): Promise<boolean> {\n  return argon2.verify(hash, password);\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/oauth/oauth.service.ts",
                          "content": "import { AuthService, Context } from \"../auth/auth.service\";\nimport User from \"../auth/user.model\";\n\ntype OAuthProfile = {\n  provider: string;\n  providerId: string;\n  name: string;\n  email: string | undefined;\n  isEmailVerified: boolean;\n  avatar: string | undefined;\n};\n\nexport class OAuthService {\n  static async handleOAuthLogin(user: OAuthProfile, context: Context) {\n    const existingUser = await User.findOne({ email: user.email });\n\n    if (existingUser) {\n      await User.findByIdAndUpdate(existingUser._id, {\n        provider: user.provider,\n        providerId: user.providerId,\n        isEmailVerified: user.isEmailVerified,\n        avatar: {\n          url: user.avatar\n        }\n      });\n      await AuthService.handleToken(\n        {\n          _id: existingUser._id.toString(),\n          role: existingUser.role,\n          isEmailVerified: existingUser.isEmailVerified,\n          email: existingUser.email\n        },\n        context\n      );\n      return existingUser;\n    }\n\n    const newUser = await User.create({\n      name: user.name,\n      email: user.email,\n      isEmailVerified: user.isEmailVerified,\n\n      provider: user.provider,\n      providerId: user.providerId,\n\n      avatar: {\n        url: user.avatar\n      }\n    });\n\n    await AuthService.handleToken(\n      {\n        _id: newUser._id.toString(),\n        role: newUser.role,\n        isEmailVerified: newUser.isEmailVerified,\n        email: newUser.email\n      },\n      context\n    );\n\n    return newUser;\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/oauth/oauth.routes.ts",
                          "content": "import { Router } from \"express\";\nimport passport from \"passport\";\nimport { githubOAuth, googleOAuth } from \"./oauth.controller\";\n\nconst router = Router();\n\nrouter.get(\n  \"/github\",\n  passport.authenticate(\"github\", { scope: [\"user:email\"] })\n);\n\nrouter.get(\n  \"/github/callback\",\n  passport.authenticate(\"github\", {\n    failureRedirect: \"/login\", //? redirect route if authenticated is failed,\n    session: false\n  }),\n  githubOAuth\n);\n\nrouter.get(\n  \"/google\",\n  passport.authenticate(\"google\", {\n    scope: [\"email\", \"profile\", \"openid\"],\n    prompt: \"consent\"\n  })\n);\n\nrouter.get(\n  \"/google/callback\",\n  passport.authenticate(\"google\", {\n    failureRedirect: \"/login\", //? redirect route if authenticated is failed\n    session: false\n  }),\n  googleOAuth\n);\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/oauth/oauth.controller.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport { Profile as GithubProfile } from \"passport-github2\";\nimport { Profile as GoogleProfile } from \"passport-google-oauth20\";\n\nimport { ApiResponse } from \"../../shared/utils/api-response\";\nimport { AsyncHandler } from \"../../shared/utils/async-handler\";\nimport { ApiError } from \"../../shared/utils/api-error\";\nimport { OAuthService } from \"./oauth.service\";\nimport { setCookies } from \"../../shared/helpers/cookie.helper\";\nimport { SESSION_EXPIRES_IN } from \"../auth/auth.constants\";\n\n//? LOGIN WITH GITHUB\nexport const githubOAuth = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const data = req.user as GithubProfile | undefined;\n\n    if (!data) {\n      return next(ApiError.unauthorized(\"Authenticated failed!\"));\n    }\n\n    const user = {\n      provider: data?.provider,\n      providerId: data.id,\n      name: data.displayName,\n      email: data?.emails && data?.emails[0]?.value,\n      isEmailVerified: true,\n      avatar: data.photos && data.photos[0].value\n    };\n\n    const existingUser = await OAuthService.handleOAuthLogin(user, {\n      setAuthCookie: (token: string) => {\n        setCookies(res, [\n          {\n            cookie: \"sid\",\n            value: token,\n            maxAge: SESSION_EXPIRES_IN\n          }\n        ]);\n      }\n    });\n\n    //? save the data into your databases\n\n    ApiResponse.ok(res, \"Signin Successfull\", {\n      user: {\n        _id: existingUser._id.toString(),\n        name: existingUser.name,\n        email: existingUser.email,\n        role: existingUser.role,\n        avatar: existingUser.avatar,\n        isEmailVerified: existingUser.isEmailVerified,\n        lastLoginAt: existingUser.lastLoginAt,\n        provider: existingUser.provider\n      }\n    });\n  }\n);\n\n//? LOGIN WITH GOOGLE\nexport const googleOAuth = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const data = req.user as GoogleProfile | undefined;\n\n    if (!data) {\n      return next(ApiError.unauthorized(\"Authenticated failed!\"));\n    }\n\n    const userInfo = {\n      provider: data?.provider,\n      providerId: data.id,\n      name: data.displayName,\n      email: data?.emails && data?.emails[0]?.value,\n      isEmailVerified:\n        (data?.emails && data?.emails[0]?.verified === true) || true,\n      avatar: data.profileUrl || (data.photos && data.photos[0].value)\n    };\n\n    const existingUser = await OAuthService.handleOAuthLogin(userInfo, {\n      setAuthCookie: (token: string) => {\n        setCookies(res, [\n          {\n            cookie: \"sid\",\n            value: token,\n            maxAge: SESSION_EXPIRES_IN\n          }\n        ]);\n      }\n    });\n\n    ApiResponse.ok(res, \"Signin Successfull\", {\n      user: {\n        _id: existingUser._id.toString(),\n        name: existingUser.name,\n        email: existingUser.email,\n        role: existingUser.role,\n        avatar: existingUser.avatar,\n        isEmailVerified: existingUser.isEmailVerified,\n        lastLoginAt: existingUser.lastLoginAt,\n        provider: existingUser.provider\n      }\n    });\n  }\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/configs/swagger.ts",
                          "content": "import swaggerUi from \"swagger-ui-express\";\nimport { Express } from \"express\";\nimport env from \"../configs/env\";\nimport fs from \"node:fs\";\n\nconst swaggerDocument = JSON.parse(\n  fs.readFileSync(new URL(\"../../docs/swagger.json\", import.meta.url), \"utf-8\")\n);\n\nexport const setupSwagger = (app: Express) => {\n  if (env.NODE_ENV !== \"development\") return;\n\n  app.use(\"/api/docs\", swaggerUi.serve, swaggerUi.setup(swaggerDocument));\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/configs/resend.ts",
                          "content": "import { Resend } from \"resend\";\nimport env from \"./env\";\n\nexport const resend = new Resend(env.RESEND_API_KEY);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/configs/passport.ts",
                          "content": "import passport from \"passport\";\nimport {\n  Strategy as GitHubStrategy,\n  Profile as GithubProfile\n} from \"passport-github2\";\n\nimport {\n  Strategy as GoogleStrategy,\n  Profile as GoogleProfile\n} from \"passport-google-oauth20\";\n\nimport env from \"./env\";\n\npassport.use(\n  new GitHubStrategy(\n    {\n      clientID: env.GITHUB_CLIENT_ID,\n      clientSecret: env.GITHUB_CLIENT_SECRET,\n      callbackURL: env.GITHUB_REDIRECT_URI\n    },\n    function (\n      accessToken: string,\n      refreshToken: string,\n      profile: GithubProfile,\n      cb: (error: Error | null, user?: any) => void\n    ) {\n      // console.log({ profile });\n      return cb(null, profile);\n    }\n  )\n);\n\npassport.use(\n  new GoogleStrategy(\n    {\n      clientID: env.GOOGLE_CLIENT_ID,\n      clientSecret: env.GOOGLE_CLIENT_SECRET,\n      callbackURL: env.GOOGLE_REDIRECT_URI\n    },\n    function (accessToken, refreshToken, profile: GoogleProfile, cb) {\n      return cb(null, profile);\n    }\n  )\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/configs/env.ts",
                          "content": "/* eslint-disable no-console */\nimport \"dotenv-flow/config\";\nimport { z } from \"zod\";\n\nexport const envSchema = z.object({\n  NODE_ENV: z\n    .enum([\"development\", \"test\", \"production\"])\n    .default(\"development\"),\n\n  PORT: z.string().regex(/^\\d+$/, \"PORT must be a number\").transform(Number),\n\n  DATABASE_URL: z.url(),\n\n  CORS_ORIGIN: z.string(),\n\n  LOG_LEVEL: z\n    .enum([\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"])\n    .default(\"info\"),\n\n  CRYPTO_SECRET: z.string().min(32),\n\n  RESEND_API_KEY: z.string(),\n  EMAIL_FROM: z.email(),\n\n  CLOUDINARY_CLOUD_NAME: z.string(),\n  CLOUDINARY_API_KEY: z.string(),\n  CLOUDINARY_API_SECRET: z.string(),\n\n  GOOGLE_CLIENT_ID: z.string(),\n  GOOGLE_CLIENT_SECRET: z.string(),\n  GOOGLE_REDIRECT_URI: z.url(),\n\n  GITHUB_CLIENT_ID: z.string(),\n  GITHUB_CLIENT_SECRET: z.string(),\n  GITHUB_REDIRECT_URI: z.url()\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\nconst result = envSchema.safeParse(process.env);\n\nif (!result.success) {\n  console.error(\"❌ Invalid environment configuration\");\n  console.error(z.prettifyError(result.error));\n  process.exit(1);\n}\n\nexport const env: Readonly<Env> = Object.freeze(result.data);\n\nexport default env;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/configs/db.ts",
                          "content": "import mongoose from \"mongoose\";\nimport env from \"./env\";\nimport { logger } from \"../utils/logger\";\n\nexport const connectDB = async (): Promise<void> => {\n  try {\n    const conn = await mongoose.connect(env.DATABASE_URL as string);\n    logger.info(`MongoDB Connected: ${conn.connection.host}`);\n  } catch (error) {\n    logger.error(error, \"MongoDB Connection Failed\");\n    process.exit(1);\n  }\n};\n"
                        },
                        {
                          "type": "file",
                          "path": "src/shared/configs/cloudinary.ts",
                          "content": "import { v2 as cloudinary } from \"cloudinary\";\nimport env from \"./env\";\n\ncloudinary.config({\n  cloud_name: env.CLOUDINARY_CLOUD_NAME,\n  api_key: env.CLOUDINARY_API_KEY,\n  api_secret: env.CLOUDINARY_API_SECRET\n});\n\nexport default cloudinary;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/health/health.routes.ts",
                          "content": "import { Router } from \"express\";\nimport { healthCheck, detailedHealthCheck } from \"./health.controller\";\n\nconst router = Router();\n\nrouter.get(\"/\", healthCheck);\nrouter.get(\"/detailed\", detailedHealthCheck);\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/health/health.controller.ts",
                          "content": "import { ApiResponse } from \"../../shared/utils/api-response\";\nimport { AsyncHandler } from \"../../shared/utils/async-handler\";\nimport { Request, Response } from \"express\";\n\n/**\n * Basic health check endpoint\n * GET /api/health\n */\nexport const healthCheck = AsyncHandler(\n  async (_req: Request, res: Response) => {\n    return ApiResponse.Success(res, \"Service is healthy\", {\n      status: \"healthy\",\n      timestamp: new Date().toISOString(),\n      uptime: process.uptime()\n    });\n  }\n);\n\n/**\n * Detailed health check with system information\n * GET /api/health/detailed\n */\nexport const detailedHealthCheck = AsyncHandler(\n  async (_req: Request, res: Response) => {\n    const healthData = {\n      status: \"healthy\",\n      timestamp: new Date().toISOString(),\n      uptime: process.uptime(),\n      environment: process.env.NODE_ENV || \"development\",\n      version: process.env.npm_package_version || \"1.0.0\",\n      memory: {\n        used:\n          Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) /\n          100,\n        total:\n          Math.round((process.memoryUsage().heapTotal / 1024 / 1024) * 100) /\n          100,\n        unit: \"MB\"\n      },\n      cpu: {\n        usage: process.cpuUsage()\n      }\n    };\n\n    return ApiResponse.Success(res, \"Service is healthy\", healthData);\n  }\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/user.model.ts",
                          "content": "import mongoose, { Document, Model, Schema } from \"mongoose\";\n\nexport interface IAvatar {\n  public_id: string;\n  url: string;\n  size: number;\n}\n\nexport interface IUser extends Document {\n  _id: mongoose.Types.ObjectId;\n  name: string;\n  email: string;\n  password?: string;\n  role: \"user\" | \"admin\";\n  isEmailVerified: boolean;\n  lastLoginAt?: Date;\n  failedLoginAttempts: number;\n  lockUntil?: Date;\n  avatar?: IAvatar;\n\n  provider: \"local\" | \"google\" | \"github\";\n  providerId?: string;\n\n  isDeleted: boolean;\n  deletedAt?: Date | null;\n  reActivateAvailableAt?: Date | null;\n\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nconst userSchema = new Schema<IUser>(\n  {\n    name: {\n      type: String,\n      required: [true, \"Name is required\"],\n      trim: true\n    },\n    email: {\n      type: String,\n      required: [true, \"Email is required\"],\n      unique: true,\n      lowercase: true,\n      trim: true\n    },\n    password: {\n      type: String,\n      select: false,\n      default: null\n    },\n    provider: {\n      type: String,\n      enum: [\"local\", \"google\", \"github\"],\n      default: \"local\"\n    },\n    providerId: {\n      type: String,\n      default: null\n    },\n    role: {\n      type: String,\n      enum: [\"user\", \"admin\"],\n      default: \"user\"\n    },\n    avatar: {\n      public_id: String,\n      url: String,\n      size: Number\n    },\n    isEmailVerified: {\n      type: Boolean,\n      default: false\n    },\n    lastLoginAt: {\n      type: Date\n    },\n    failedLoginAttempts: {\n      type: Number,\n      required: true,\n      default: 0\n    },\n    lockUntil: {\n      type: Date\n    },\n    isDeleted: {\n      type: Boolean,\n      default: false\n    },\n    deletedAt: {\n      type: Date,\n      default: null\n    },\n    reActivateAvailableAt: {\n      type: Date,\n      default: null\n    }\n  },\n  {\n    timestamps: true\n  }\n);\n\n// Performance Indexes\nuserSchema.index({ provider: 1, providerId: 1 }); // Quick lookup for OAuth\nuserSchema.index({ role: 1 });\nuserSchema.index({ isDeleted: 1 }); // Optimized for soft-delete queries\n\nconst User: Model<IUser> =\n  mongoose.models.User || mongoose.model<IUser>(\"User\", userSchema);\n\nexport default User;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/session.model.ts",
                          "content": "import mongoose, { Document, Schema, Model } from \"mongoose\";\nimport { SESSION_EXPIRES_IN } from \"./auth.constants\";\n\nexport interface ISession extends Document {\n  _id: mongoose.Types.ObjectId;\n  userId: mongoose.Types.ObjectId;\n  tokenHash: string;\n  ip?: string;\n  userAgent?: string;\n  isActive: boolean;\n  lastUsedAt: Date;\n  expiresAt: Date;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nconst sessionSchema: Schema<ISession> = new Schema(\n  {\n    userId: {\n      type: mongoose.Schema.Types.ObjectId,\n      ref: \"User\",\n      required: true\n    },\n    tokenHash: {\n      type: String,\n      required: true,\n      unique: true\n    },\n    ip: {\n      type: String\n    },\n    userAgent: {\n      type: String\n    },\n    isActive: {\n      type: Boolean,\n      default: true\n    },\n    lastUsedAt: {\n      type: Date,\n      default: Date.now\n    },\n    expiresAt: {\n      type: Date,\n      required: true\n    }\n  },\n  {\n    timestamps: true\n  }\n);\n\n// Supporting indexes (non-TTL)\nsessionSchema.index({ userId: 1, isActive: 1 });\nsessionSchema.index({ userId: 1, lastUsedAt: -1 });\nsessionSchema.index(\n  { createdAt: 1 },\n  { expireAfterSeconds: SESSION_EXPIRES_IN / 1000 } // 7 days\n);\n\nconst Session: Model<ISession> =\n  mongoose.models.Session || mongoose.model<ISession>(\"Session\", sessionSchema);\n\nexport default Session;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/auth.validation.ts",
                          "content": "import * as z from \"zod\";\nimport { OTP_TYPES } from \"./auth.constants\";\n\nexport const nameSchema = z\n  .string({ error: \"Name must be a string\" })\n  .trim()\n  .min(3, {\n    message: \"Name must be at least 3 characters long\"\n  })\n  .max(50, {\n    message: \"Name must be at most 50 characters long\"\n  });\n\nexport const passwordSchema = z\n  .string({ error: \"Password must be a string\" })\n  .trim()\n  .min(6, {\n    message: \"Password must be at least 6 characters long\"\n  })\n  .max(80, {\n    message: \"Password must be at most 80 characters long\"\n  });\n\nexport const emailSchema = z\n  .email({ message: \"Please enter a valid email address.\" })\n  .max(100, { message: \"Email must be no more than 100 characters.\" });\n\nexport const roleSchema = z\n  .enum([\"user\", \"admin\"], {\n    error: \"Role must be either applicant, recruiter, or admin\"\n  })\n  .default(\"user\");\n\nexport const SigninSchema = z.object({\n  email: emailSchema,\n  password: z.string({ error: \"Password must be a string\" }).trim().min(1, {\n    message: \"Password is required\"\n  })\n});\n\nexport const SignupSchema = z\n  .object({\n    name: nameSchema,\n    email: emailSchema,\n    password: passwordSchema,\n    confirmPassword: passwordSchema,\n    role: roleSchema\n  })\n  .refine(\n    data => {\n      return data.password === data.confirmPassword;\n    },\n    {\n      message: \"Passwords do not match\",\n      path: [\"confirmPassword\"]\n    }\n  );\n\nexport const RequestOtpSchema = z.object({\n  email: emailSchema,\n  otpType: z.enum(OTP_TYPES, { error: \"Invalid otp type\" })\n});\n\nexport const VerifyOtpSchema = z.object({\n  otpCode: z.string().min(6, \"Please enter a valid OTP\"),\n  email: emailSchema,\n  otpType: z.enum(OTP_TYPES, { error: \"Invalid otp type\" })\n});\n\nexport const ResetPasswordSchema = z.object({\n  email: emailSchema,\n  newPassword: passwordSchema\n});\n\nexport const ChangePasswordSchema = z.object({\n  oldPassword: z.string({ error: \"Password must be a string\" }).min(1, {\n    message: \"Old password is required\"\n  }),\n  newPassword: passwordSchema\n});\n\nexport const UpdateProfileSchema = z.object({\n  name: nameSchema.optional(),\n  avatar: z.string().optional()\n});\n\nexport const GoogleSigninSchema = z.object({\n  name: nameSchema,\n  email: emailSchema,\n  provider: z.enum([\"google\", \"github\"]).default(\"google\"),\n  providerId: z.string({ error: \"Provider id must be a string\" }).min(1, {\n    message: \"Provider id is required\"\n  }),\n  avatar: z.string().optional(),\n  isEmailVerified: z.boolean().default(false)\n});\n\nexport const DeleteAccountSchema = z.object({\n  userId: z.string({ error: \"User id must be a string\" }).min(1, {\n    message: \"User id is required\"\n  }),\n  type: z\n    .enum([\"soft\", \"hard\"], { error: \"Type must be either soft or hard\" })\n    .default(\"soft\")\n});\n\nexport type SignupUserType = z.infer<typeof SignupSchema>;\nexport type SigninUserType = z.infer<typeof SigninSchema>;\nexport type RequestOtpType = z.infer<typeof RequestOtpSchema>;\nexport type VerifyOtpType = z.infer<typeof VerifyOtpSchema>;\nexport type ResetPasswordType = z.infer<typeof ResetPasswordSchema>;\nexport type ChangePasswordType = z.infer<typeof ChangePasswordSchema>;\nexport type UpdateProfileType = z.infer<typeof UpdateProfileSchema>;\nexport type GoogleSigninType = z.infer<typeof GoogleSigninSchema>;\nexport type DeleteAccountType = z.infer<typeof DeleteAccountSchema>;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/auth.types.ts",
                          "content": "import { OTP_TYPES } from \"./auth.constants\";\n\nexport type OTPType = (typeof OTP_TYPES)[number];\n\nexport interface IUser {\n  _id: string;\n  name: string;\n  email: string;\n  password?: string;\n  role: \"user\" | \"admin\";\n  isEmailVerified: boolean;\n  lastLoginAt?: Date;\n  failedLoginAttempts: number;\n  lockUntil?: Date;\n  avatar?: {\n    url: string;\n    publicId: string;\n    size: number;\n  };\n  provider: \"local\" | \"google\" | \"github\";\n  providerId?: string;\n  isDeleted: boolean;\n  deletedAt?: Date;\n  reActivateAvailableAt?: Date;\n  createdAt: Date;\n  updatedAt: Date;\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/auth.service.ts",
                          "content": "import { NextFunction } from \"express\";\nimport { SignupUserType } from \"./auth.validation\";\nimport {\n  LOGIN_MAX_ATTEMPTS,\n  MAX_SESSIONS_PER_USER,\n  REACTIVATION_AVAILABLE_AT,\n  SESSION_EXPIRES_IN\n} from \"./auth.constants\";\n\nimport mongoose from \"mongoose\";\nimport { OtpService } from \"../otp/otp.service\";\nimport { deleteFileFromCloudinary } from \"../upload/upload.service\";\nimport env from \"../../shared/configs/env\";\nimport { ApiError } from \"@/shared/utils/api-error\";\nimport User from \"./user.model\";\nimport Session from \"./session.model\";\nimport {\n  generateHashedToken,\n  generateSecureToken\n} from \"@/shared/helpers/token.helpers\";\nimport { SendMail, sendEmail } from \"@/shared/utils/send-mail\";\nimport { hashPassword, verifyPassword } from \"@/shared/helpers/auth.helpers\";\nimport { IUser } from \"./auth.types\";\n\nexport type Context = {\n  setAuthCookie?: (token: string) => void;\n  ip?: string;\n  userAgent?: string;\n};\n\nexport class AuthService {\n  static async registerUser(\n    next: NextFunction,\n    user: Omit<SignupUserType, \"confirmPassword\">\n  ) {\n    const { name, email, password, role } = user;\n    const existingUser = await User.findOne({ email }).select(\"+password\");\n\n    if (existingUser) {\n      return next(ApiError.conflict(\"User with this email already exists\"));\n    }\n\n    const hashedPassword = await hashPassword(password);\n\n    const newUser = await User.create({\n      name,\n      email,\n      password: hashedPassword,\n      role\n    });\n\n    return newUser;\n  }\n\n  static async loginAndSendOtp(\n    next: NextFunction,\n    { email, password }: { email: string; password: string }\n  ) {\n    const session = await mongoose.startSession();\n\n    try {\n      session.startTransaction();\n\n      const user = await User.findOne({ email })\n        .session(session)\n        .select(\"+password\");\n      if (!user) {\n        await session.abortTransaction();\n        return next(ApiError.unauthorized(\"Invalid credentials\"));\n      }\n\n      const isPasswordValid = await verifyPassword(\n        password,\n        user.password || \"\"\n      );\n      if (!isPasswordValid) {\n        await session.abortTransaction();\n        return next(ApiError.unauthorized(\"Invalid credentials\"));\n      }\n\n      const otp = await OtpService.sendOtp(next, {\n        email,\n        otpType: \"signin\",\n        subject: \"Signin\"\n      });\n\n      if (!otp) {\n        await session.abortTransaction();\n        return next(ApiError.server(\"Failed to generate OTP\"));\n      }\n\n      await session.commitTransaction();\n      session.endSession();\n\n      return {\n        message: otp.message\n      };\n    } catch (err) {\n      await session.abortTransaction();\n      session.endSession();\n      return next(ApiError.server(\"Signin failed\"));\n    }\n  }\n\n  static async handleToken(\n    user: Pick<IUser, \"isEmailVerified\" | \"_id\" | \"role\" | \"email\">,\n    context: Context\n  ) {\n    if (!user.isEmailVerified) {\n      await User.updateOne(\n        { _id: user._id },\n        { $set: { isEmailVerified: true } }\n      );\n    }\n\n    if (context.userAgent) {\n      const existingDevice = await Session.findOne({\n        userId: user._id,\n        userAgent: context.userAgent\n      });\n\n      const isNewDevice = !existingDevice;\n      if (isNewDevice) {\n        const html = `\n        <p>New sign-in to your account from a new device or browser.</p>\n        <p>This could be a sign of a possible security threat.</p>\n        <p> \n          <strong>Device:</strong> ${context.userAgent}<br/>\n          <strong>IP Address:</strong> ${context.ip || \"Unknown\"}<br/>\n          <strong>Time:</strong> ${new Date().toDateString()}<br/>\n        </p>\n        <p>If this was you, you can safely ignore this email. If not, please secure your account immediately.</p>\n        `;\n\n        await sendEmail({\n          from: env.EMAIL_FROM,\n          email: user.email,\n          subject: \"New Sign-in Alert\",\n          html\n        } as SendMail);\n      }\n    }\n\n    const token = generateSecureToken();\n    const hashedToken = generateHashedToken(token);\n    const activeSessions = await Session.countDocuments({\n      userId: user._id,\n      isActive: true\n    });\n\n    if (activeSessions >= MAX_SESSIONS_PER_USER) {\n      await Session.findOneAndUpdate(\n        { userId: user._id, isActive: true },\n        { isActive: false },\n        { sort: { lastUsedAt: 1 } }\n      );\n    }\n\n    await Session.create({\n      userId: user._id,\n      tokenHash: hashedToken,\n      expiresAt: new Date(Date.now() + SESSION_EXPIRES_IN),\n      ip: context.ip,\n      userAgent: context.userAgent,\n      isActive: true\n    });\n\n    context.setAuthCookie && context.setAuthCookie(token);\n\n    await User.updateOne(\n      { _id: user._id },\n      {\n        $set: { lastLogin: new Date(), failedLoginAttempts: 0 },\n        $unset: { lockUntil: 1 }\n      }\n    );\n    return { message: \"OTP verified and user signed in successfully\" };\n  }\n\n  static async getUserProfile(userId: string) {\n    const user = await User.findById(userId);\n    return user;\n  }\n\n  static async forgotPassword(next: NextFunction, email: string) {\n    const user = await User.findOne({ email });\n\n    if (!user) {\n      return next(\n        ApiError.badRequest(\"If this email is registered, check your inbox.\")\n      );\n    }\n\n    const result = await OtpService.sendOtp(next, {\n      email,\n      otpType: \"password-reset\",\n      subject: \"Password Reset\"\n    });\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to send otp!\"));\n    }\n\n    return result;\n  }\n\n  static async resetPassword(\n    next: NextFunction,\n    email: string,\n    newPassword: string\n  ) {\n    const user = await User.findOne({ email }).select(\"+password\");\n\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (user.lockUntil && new Date(user.lockUntil) > new Date()) {\n      return next(\n        ApiError.forbidden(\n          `Your account has been locked. Please try again after ${Math.ceil(\n            (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    if (user.failedLoginAttempts >= LOGIN_MAX_ATTEMPTS && user.lockUntil) {\n      return next(\n        ApiError.forbidden(\n          `You have exceeded the maximum number of login attempts. Please try again after ${Math.ceil(\n            (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    if (!user.isEmailVerified) {\n      return next(ApiError.unauthorized(\"Please verify your email first.\"));\n    }\n\n    const oldPassword = user.password;\n\n    const isOldPassword = await verifyPassword(\n      newPassword,\n      oldPassword as string\n    );\n\n    if (isOldPassword) {\n      return next(ApiError.badRequest(\"New password should be different!\"));\n    }\n\n    const hashedPassword = await hashPassword(newPassword);\n    await User.updateOne(\n      { email },\n      {\n        $set: {\n          password: hashedPassword,\n          isEmailVerified: true\n        }\n      }\n    );\n    return { message: \"Password reset successfully!\" };\n  }\n\n  static async changePassword(\n    next: NextFunction,\n    {\n      newPassword,\n      oldPassword,\n      userId\n    }: { userId: string; newPassword: string; oldPassword: string }\n  ) {\n    const user = await User.findById(userId).select(\"+password\");\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (!user.isEmailVerified) {\n      return next(ApiError.unauthorized(\"Please verify your email first.\"));\n    }\n\n    const isOldPassword = await verifyPassword(\n      oldPassword,\n      user.password || \"\"\n    );\n\n    if (!isOldPassword) {\n      return next(ApiError.unauthorized(\"Invalid credentials\"));\n    }\n\n    if (newPassword === oldPassword) {\n      return next(ApiError.badRequest(\"New password should be different!\"));\n    }\n\n    const hashedPassword = await hashPassword(newPassword);\n    await User.updateOne(\n      { _id: userId },\n      {\n        $set: {\n          password: hashedPassword\n        }\n      }\n    );\n    return { message: \"Password changed successfully. Please login again!\" };\n  }\n\n  static async deleteOrDeactiveAccount(\n    next: NextFunction,\n    userId: string,\n    type: \"soft\" | \"hard\"\n  ) {\n    const user = await User.findById(userId);\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (type === \"soft\") {\n      user.isDeleted = true;\n      user.deletedAt = new Date();\n      user.reActivateAvailableAt = new Date(\n        Date.now() + REACTIVATION_AVAILABLE_AT\n      );\n      await user.save();\n    } else if (type === \"hard\") {\n      if (user?.avatar?.public_id) {\n        await deleteFileFromCloudinary([user.avatar.public_id]);\n      }\n      await User.findOneAndDelete({ _id: userId });\n      await user.save();\n    }\n  }\n\n  static async reactivateAccount(next: NextFunction, userId: string) {\n    const user = await User.findById(userId);\n    if (!user) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (user.lockUntil && new Date(user.lockUntil) > new Date()) {\n      return next(\n        ApiError.badRequest(\n          `Your account has been locked. Please try again after ${Math.ceil(\n            (user.lockUntil.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    if (!user?.isDeleted || !user?.deletedAt) {\n      return next(ApiError.badRequest(\"Your account is already active!\"));\n    }\n\n    if (\n      user?.reActivateAvailableAt &&\n      new Date(user?.reActivateAvailableAt) > new Date()\n    ) {\n      return next(\n        ApiError.forbidden(\n          `Your account has been locked. Please try again after ${Math.ceil(\n            (user.reActivateAvailableAt.getTime() - Date.now()) / (1000 * 60)\n          )} minutes.`\n        )\n      );\n    }\n\n    await User.findOneAndUpdate(\n      { _id: userId },\n      {\n        $set: {\n          isDeleted: false,\n          deletedAt: null,\n          reActivateAvailableAt: null\n        }\n      },\n      { new: true }\n    );\n\n    await user.save();\n  }\n\n  static async getUserSessions(userId: string) {\n    const session = await Session.aggregate([\n      {\n        $match: { userId: new mongoose.Types.ObjectId(userId), isActive: true }\n      },\n      {\n        $project: {\n          _id: 1,\n          ip: 1,\n          userAgent: 1,\n          lastUsedAt: 1,\n          expiresAt: 1,\n          isActive: 1,\n          userId: 1\n        }\n      },\n      {\n        $lookup: {\n          from: \"users\",\n          localField: \"userId\",\n          foreignField: \"_id\",\n          as: \"user\",\n          pipeline: [\n            {\n              $project: {\n                _id: 1,\n                name: 1,\n                email: 1\n              }\n            }\n          ]\n        }\n      },\n      {\n        $unwind: {\n          path: \"$user\",\n          preserveNullAndEmptyArrays: true\n        }\n      }\n    ]);\n    return session;\n  }\n\n  static async deleteUserSession(userId: string, sessionId: string) {\n    return await Session.findOneAndDelete({\n      userId,\n      _id: sessionId\n    });\n  }\n\n  static async logoutUser(userId: string, sessionId: string) {\n    return await Session.deleteMany({ userId, _id: sessionId });\n  }\n\n  static async deleteAllUserSessions(userId: string) {\n    return await Session.findOneAndDelete({\n      userId\n    });\n  }\n}\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/auth.routes.ts",
                          "content": "import { Router } from \"express\";\nimport { validateRequest } from \"../../shared/middlewares/validate-request\";\nimport {\n  ChangePasswordSchema,\n  DeleteAccountSchema,\n  RequestOtpSchema,\n  ResetPasswordSchema,\n  SigninSchema,\n  SignupSchema,\n  UpdateProfileSchema,\n  VerifyOtpSchema\n} from \"./auth.validation\";\nimport {\n  changePassword,\n  deleteAccount,\n  deleteAllUserSessions,\n  deleteUserSession,\n  forgotPassword,\n  getUserProfile,\n  getUserSessions,\n  logoutUser,\n  reactivateAccount,\n  resetPassword,\n  signinUser,\n  signupUser,\n  updateProfile,\n  verifyOtp\n} from \"./auth.controller\";\nimport { verifyAuthentication } from \"../../shared/middlewares/verify-auth\";\nimport { checkUserAccountRestriction } from \"../../shared/middlewares/user-account-restriction\";\nimport {\n  changePasswordLimiter,\n  deleteAccountLimiter,\n  otpRequestLimiter,\n  otpVerificationLimiter,\n  resetPasswordLimiter,\n  signinRateLimiter,\n  signupRateLimiter\n} from \"../../shared/middlewares/rate-limiter\";\nimport upload from \"../../shared/middlewares/upload-file\";\nimport { validateObjectId } from \"../../shared/middlewares/validate-id\";\n\nconst router = Router();\n\nrouter.post(\n  \"/verify-otp\",\n  validateRequest(VerifyOtpSchema),\n  otpVerificationLimiter,\n  verifyOtp\n);\n\nrouter.post(\n  \"/signup\",\n  validateRequest(SignupSchema),\n  signupRateLimiter,\n  signupUser\n);\n\nrouter.post(\n  \"/signin\",\n  validateRequest(SigninSchema),\n  signinRateLimiter,\n  signinUser\n);\n\nrouter.get(\"/profile\", verifyAuthentication, getUserProfile);\n\nrouter.get(\"/sessions\", verifyAuthentication, getUserSessions);\n\nrouter.delete(\"/sessions\", verifyAuthentication, deleteAllUserSessions);\n\nrouter.delete(\n  \"/sessions/:sessionId\",\n  validateObjectId(\"sessionId\"),\n  verifyAuthentication,\n  deleteUserSession\n);\n\nrouter.patch(\n  \"/profile\",\n  upload.single(\"avatar\"),\n  validateRequest(UpdateProfileSchema),\n  verifyAuthentication,\n  checkUserAccountRestriction,\n  updateProfile\n);\n\nrouter.post(\n  \"/logout\",\n  verifyAuthentication,\n  checkUserAccountRestriction,\n  logoutUser\n);\n\nrouter.post(\n  \"/forgot-password\",\n  validateRequest(RequestOtpSchema.pick({ email: true })),\n  otpRequestLimiter,\n  forgotPassword\n);\n\nrouter.post(\n  \"/reset-password\",\n  validateRequest(ResetPasswordSchema),\n  resetPasswordLimiter,\n  resetPassword\n);\n\nrouter.post(\n  \"/change-password\",\n  verifyAuthentication,\n  validateRequest(ChangePasswordSchema),\n  checkUserAccountRestriction,\n  changePasswordLimiter,\n  changePassword\n);\n\nrouter.delete(\n  \"/delete-account\",\n  verifyAuthentication,\n  validateRequest(DeleteAccountSchema),\n  checkUserAccountRestriction,\n  deleteAccountLimiter,\n  deleteAccount\n);\n\nrouter.put(\"/reactivate-account\", verifyAuthentication, reactivateAccount);\n\nexport default router;\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/auth.controller.ts",
                          "content": "import { NextFunction, Request, Response } from \"express\";\nimport { ApiResponse } from \"../../shared/utils/api-response\";\nimport { AsyncHandler } from \"../../shared/utils/async-handler\";\n\nimport { ApiError } from \"../../shared/utils/api-error\";\nimport { AuthService } from \"./auth.service\";\nimport { OtpService } from \"../otp/otp.service\";\nimport { clearCookie, setCookies } from \"../../shared/helpers/cookie.helper\";\nimport {\n  deleteFileFromCloudinary,\n  uploadToCloudinary\n} from \"../upload/upload.service\";\nimport {\n  RESET_PASSWORD_TOKEN_EXPIRY,\n  SESSION_EXPIRES_IN\n} from \"./auth.constants\";\nimport { DeleteAccountType, VerifyOtpType } from \"./auth.validation\";\nimport { UserRequest } from \"@/types/global\";\n\n//? VERIFY OTP\nexport const verifyOtp = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { email, otpCode, otpType }: VerifyOtpType = req.body;\n    if (!email || !otpCode || !otpType) {\n      return next(\n        ApiError.badRequest(\"Email, OTP code and OTP type are required\")\n      );\n    }\n\n    const otp = await OtpService.verifyOtp(\n      next,\n      { email, otpCode, otpType },\n      {\n        setAuthCookie: (token: string) => {\n          setCookies(res, [\n            {\n              cookie: \"sid\",\n              value: token,\n              maxAge: SESSION_EXPIRES_IN\n            }\n          ]);\n        },\n        ip: req.ip || \"unknown\",\n        userAgent: req.headers[\"user-agent\"] || \"\"\n      },\n      {\n        setCookie: (token: string) => {\n          setCookies(res, [\n            {\n              cookie: \"resetPasswordToken\",\n              value: token,\n              maxAge: RESET_PASSWORD_TOKEN_EXPIRY\n            }\n          ]);\n        }\n      }\n    );\n\n    if (!otp) {\n      return next(ApiError.server(\"Failed to verify OTP!\"));\n    }\n\n    return ApiResponse.ok(res, otp?.message || \"OTP verified successfully!\");\n  }\n);\n\n//? SIGNUP USER\nexport const signupUser = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { name, email, password, role } = req.body;\n    if (!name || !email || !password) {\n      return next(ApiError.badRequest(\"Name, email and password are required\"));\n    }\n\n    const user = await AuthService.registerUser(next, {\n      name,\n      email,\n      password,\n      role\n    });\n\n    if (!user) {\n      return next(ApiError.server(\"Failed to register user!\"));\n    }\n\n    return ApiResponse.created(res, \"User registered successfully\", {\n      name: user.name,\n      email: user.email,\n      role: user.role\n    });\n  }\n);\n\n//? SIGNIN USER\nexport const signinUser = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { email, password } = req.body;\n    if (!email || !password) {\n      return next(ApiError.badRequest(\"Email and password are required\"));\n    }\n\n    const result = await AuthService.loginAndSendOtp(next, { email, password });\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to login!\"));\n    }\n\n    return ApiResponse.ok(res, result.message || \"Otp sent successfully!\");\n  }\n);\n\n//? GET USER PROFILE\nexport const getUserProfile = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req.user?._id;\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const user = await AuthService.getUserProfile(userId.toString());\n    if (!user) {\n      return next(ApiError.notFound(\"User not found\"));\n    }\n\n    if (user.isDeleted) {\n      return next(ApiError.notFound(\"This account has been deactivated.\"));\n    }\n\n    return ApiResponse.ok(res, \"User profile fetched successfully\", {\n      user: {\n        _id: user._id,\n        name: user.name,\n        email: user.email,\n        role: user.role,\n        avatar: user.avatar,\n        isEmailVerified: user.isEmailVerified,\n        lastLoginAt: user.lastLoginAt\n      }\n    });\n  }\n);\n\n//? UPDATE PROFILE\nexport const updateProfile = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const data = req.body;\n    const { name } = data;\n\n    if (!req.user?._id) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const user = await AuthService.getUserProfile(req.user?._id.toString());\n\n    if (!user) {\n      return next(ApiError.notFound(\"User not found\"));\n    }\n\n    if (req?.file && user?.avatar?.public_id) {\n      await deleteFileFromCloudinary([user.avatar.public_id]);\n    }\n\n    if (req?.file && user?.avatar) {\n      const file = await uploadToCloudinary(req.file.buffer, {\n        folder: \"uploads/files\",\n        resource_type: \"auto\"\n      });\n      user.avatar = {\n        public_id: req.file\n          ? file.public_id\n          : (user?.avatar?.public_id as string),\n        url: req.file ? file.url : (user.avatar.url as string),\n        size: req.file ? file.size : (user.avatar.size as number)\n      };\n    }\n\n    if (name) {\n      user.name = name;\n    }\n\n    await user.save();\n\n    return ApiResponse.Success(res, \"Profile updated successfully!\", user);\n  }\n);\n\n//? LOGOUT\nexport const logoutUser = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req.user?._id;\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const currentSessionId = req.session?._id;\n    if (!currentSessionId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    await AuthService.logoutUser(userId.toString(), currentSessionId);\n\n    clearCookie(res, \"sid\");\n\n    return ApiResponse.Success(res, \"Logged out successfully!\");\n  }\n);\n\n//? FORGOT PASSWORD\nexport const forgotPassword = AsyncHandler(\n  async (req: Request, res: Response, next: NextFunction) => {\n    const { email } = req.body;\n    if (!email) {\n      return next(ApiError.badRequest(\"Email is required!\"));\n    }\n\n    const result = await AuthService.forgotPassword(next, email);\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to send otp!\"));\n    }\n\n    return ApiResponse.ok(res, result.message || \"Otp sent successfully!\");\n  }\n);\n\n//? RESET PASSWORD\nexport const resetPassword = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const { newPassword, email } = req.body;\n    if (!email || !newPassword) {\n      return next(ApiError.badRequest(\"Newpassword and email are required!\"));\n    }\n\n    const hashedResetPasswordToken = req.cookies?.hashedResetPasswordToken;\n\n    if (!hashedResetPasswordToken) {\n      return next(\n        ApiError.badRequest(\"Reset password token not found or expired\")\n      );\n    }\n\n    const result = await AuthService.resetPassword(next, email, newPassword);\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to reset password!\"));\n    }\n\n    res.clearCookie(\"hashedResetPasswordToken\");\n    return ApiResponse.ok(\n      res,\n      result.message || \"Password reset successfully!\"\n    );\n  }\n);\n\n//? CHANGE PASSWORD\nexport const changePassword = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const { oldPassword, newPassword } = req.body;\n\n    if (!oldPassword || !newPassword) {\n      return next(\n        ApiError.badRequest(\"Old password and new password are required\")\n      );\n    }\n\n    const result = await AuthService.changePassword(next, {\n      userId: userId.toString(),\n      oldPassword,\n      newPassword\n    });\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to change password!\"));\n    }\n\n    clearCookie(res, \"sid\");\n\n    return ApiResponse.ok(\n      res,\n      result.message || \"Password changed successfully!\"\n    );\n  }\n);\n\n//? DELETE/DEACTIVATE ACCOUNT\nexport const deleteAccount = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const { userId, type }: DeleteAccountType = req.body;\n\n    if (!userId || !type) {\n      return next(ApiError.badRequest(\"User id and type are required!\"));\n    }\n\n    const reqUserId = req?.user?._id;\n\n    if (!reqUserId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    if (userId !== reqUserId) {\n      return next(\n        ApiError.unauthorized(\"you are not authorized to perform this action\")\n      );\n    }\n\n    await AuthService.deleteOrDeactiveAccount(next, userId, type);\n\n    if (type === \"hard\") {\n      clearCookie(res, \"sid\");\n    }\n\n    return ApiResponse.Success(\n      res,\n      `Account ${type === \"soft\" ? \"deactivated\" : \"deleted\"} successfully!`\n    );\n  }\n);\n\n//? REACTIVATE ACCOUNT\nexport const reactivateAccount = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    await AuthService.reactivateAccount(next, userId);\n\n    return ApiResponse.Success(res, \"Account reactivated successfully!\");\n  }\n);\n\n//? GET USER SESSIONS\nexport const getUserSessions = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n    const currentSessionId = req.session?._id;\n\n    if (!userId || !currentSessionId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const result = await AuthService.getUserSessions(userId.toString());\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to get user sessions!\"));\n    }\n\n    return ApiResponse.ok(res, \"User sessions fetched successfully\", result);\n  }\n);\n\n//? DELETE SESSION\nexport const deleteUserSession = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n    const { sessionId } = req.params;\n\n    if (!userId || !sessionId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const result = await AuthService.deleteUserSession(\n      userId,\n      sessionId as string\n    );\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to delete user session!\"));\n    }\n\n    const reqSId = req.cookies?.sid;\n\n    const isCurrentSession = sessionId === reqSId;\n    if (isCurrentSession) {\n      clearCookie(res, \"sid\");\n    }\n\n    return ApiResponse.Success(res, \"User session deleted successfully!\");\n  }\n);\n\n//? DELETE ALL SESSIONS\nexport const deleteAllUserSessions = AsyncHandler(\n  async (req: UserRequest, res: Response, next: NextFunction) => {\n    const userId = req?.user?._id;\n\n    if (!userId) {\n      return next(ApiError.unauthorized(\"Unauthorized access\"));\n    }\n\n    const result = await AuthService.deleteAllUserSessions(userId);\n\n    if (!result) {\n      return next(ApiError.server(\"Failed to delete user sessions!\"));\n    }\n\n    res.clearCookie(\"sid\");\n\n    return ApiResponse.Success(res, \"User sessions deleted successfully!\");\n  }\n);\n"
                        },
                        {
                          "type": "file",
                          "path": "src/modules/auth/auth.constants.ts",
                          "content": "export const OTP_MAX_ATTEMPTS = 5;\n\nexport const OTP_TYPES = [\n  \"signin\",\n  \"email-verification\",\n  \"password-reset\",\n  \"password-change\"\n] as const;\n\nexport const NEXT_OTP_DELAY = 1 * 60 * 1000; // 1 minute\n\nexport const LOGIN_MAX_ATTEMPTS = 5 as const;\n\nexport const OTP_CODE_LENGTH = 6 as const;\n\nexport const OTP_EXPIRES_IN = 5 * 60 * 1000; // 5 minutes\n\nexport const LOCK_TIME_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport const ACCESS_TOKEN_EXPIRY = 15 * 60 * 1000; // 15 minutes\n\nexport const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nexport const RESET_PASSWORD_TOKEN_EXPIRY = 5 * 60 * 1000; // 5 minutes\n\nexport const REACTIVATION_AVAILABLE_AT = 1 * 60 * 1000; // 24 hours\n\nexport const SESSION_EXPIRES_IN = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nexport const MAX_SESSIONS_PER_USER = 3;\n"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
