diff --git a/.dockerignore b/.dockerignore index bdb87c7..53b26c4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,9 @@ node_modules -npm-debug.log -Dockerfile +dist .git .gitignore -README.md +Dockerfile +docker-compose.yml +*.log .env -dist +uploads \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 254f135..d439d1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,40 @@ # ---------- BUILD STAGE ---------- -FROM node:18-alpine AS builder +FROM node:20-alpine AS builder WORKDIR /app +# Only install deps first (cache friendly) COPY package*.json ./ -RUN npm install +RUN npm ci +# Copy source COPY . . + +# Generate prisma + build +RUN npx prisma generate RUN npm run build # ---------- PRODUCTION STAGE ---------- -FROM node:18-alpine +FROM node:20-alpine WORKDIR /app -COPY package*.json ./ -RUN npm install && npm cache clean --force +ENV NODE_ENV=production +# Only install production deps +COPY package*.json ./ +RUN npm ci --omit=dev && npm cache clean --force + +# Copy Prisma generated client + schema +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma +COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma +COPY --from=builder /app/prisma ./prisma + +# Copy built app COPY --from=builder /app/dist ./dist - + +# Uploads folder RUN mkdir -p /app/uploads EXPOSE 5000 diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 0000000..73f1553 --- /dev/null +++ b/dist/app.js @@ -0,0 +1,37 @@ +import cookieParser from 'cookie-parser'; +import cors from 'cors'; +import express from 'express'; +import swaggerJSDoc from 'swagger-jsdoc'; +import swaggerUi from "swagger-ui-express"; +import globalErrorHandler from './app/middlewares/global_error_handler.js'; +import notFound from './app/middlewares/not_found_api.js'; +import appRouter from './routes.js'; +import { swaggerOptions } from './swaggerOptions.js'; +// define app +const app = express(); +const swaggerSpec = swaggerJSDoc(swaggerOptions); +app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); +// middleware +app.use(cors({ + origin: ["http://localhost:3000"], + methods: ["GET", "POST", "PATCH", "DELETE", "PUT"], + credentials: true +})); +app.use(express.json({ limit: "100mb" })); +app.use(express.raw()); +app.use(cookieParser()); +app.use(express.urlencoded({ extended: true })); +app.use("/api", appRouter); +// stating point +app.get('/', (req, res) => { + res.status(200).json({ + status: 'success', + message: 'Server is running successful !!', + data: null, + }); +}); +// global error handler +app.use(globalErrorHandler); +app.use(notFound); +// export app +export default app; diff --git a/dist/app/configs/index.js b/dist/app/configs/index.js new file mode 100644 index 0000000..96427ee --- /dev/null +++ b/dist/app/configs/index.js @@ -0,0 +1,25 @@ +import "dotenv/config"; +export const configs = { + port: process.env.PORT, + env: process.env.NODE_ENV, + db_url: process.env.DATABASE_URL, + jwt: { + access_token: process.env.ACCESS_TOKEN, + refresh_token: process.env.REFRESH_TOKEN, + access_expires: process.env.ACCESS_EXPIRES, + refresh_expires: process.env.REFRESH_EXPIRES, + reset_secret: process.env.RESET_SECRET, + reset_expires: process.env.RESET_EXPIRES, + front_end_url: process.env.FRONT_END_URL, + verified_token: process.env.VERIFIED_TOKEN, + }, + email: { + app_email: process.env.APP_USER_EMAIL, + app_password: process.env.APP_PASSWORD, + }, + cloudinary: { + cloud_name: process.env.CLOUD_NAME, + cloud_api_key: process.env.CLOUD_API_KEY, + cloud_api_secret: process.env.CLOUD_API_SECRET, + }, +}; diff --git a/dist/app/errors/zodError.js b/dist/app/errors/zodError.js new file mode 100644 index 0000000..77da904 --- /dev/null +++ b/dist/app/errors/zodError.js @@ -0,0 +1,15 @@ +const handleZodError = (err) => { + const errorSources = err.issues.map((issue) => { + return { + path: issue?.path[issue.path.length - 1], + message: issue.message + }; + }); + const statusCode = 400; + return { + statusCode, + message: 'Validation Error', + errorSources + }; +}; +export default handleZodError; diff --git a/dist/app/lib/prisma.js b/dist/app/lib/prisma.js new file mode 100644 index 0000000..e9b8c5b --- /dev/null +++ b/dist/app/lib/prisma.js @@ -0,0 +1,8 @@ +import { PrismaPg } from "@prisma/adapter-pg"; +import pkg from "@prisma/client"; +import "dotenv/config"; +const { PrismaClient } = pkg; +const connectionString = `${process.env.DATABASE_URL}`; +const adapter = new PrismaPg({ connectionString }); +const prisma = new PrismaClient({ adapter }); +export { prisma }; diff --git a/dist/app/middlewares/auth.js b/dist/app/middlewares/auth.js new file mode 100644 index 0000000..20cd2c0 --- /dev/null +++ b/dist/app/middlewares/auth.js @@ -0,0 +1,23 @@ +import { configs } from "../configs/index.js"; +import { AppError } from "../utils/app_error.js"; +import { jwtHelpers } from "../utils/JWT.js"; +const auth = (...roles) => { + return async (req, res, next) => { + try { + const token = req.headers.authorization || req.cookies.access_token; + if (!token) { + throw new AppError("You are not authorize!!", 401); + } + const verifiedUser = jwtHelpers.verifyToken(token, configs.jwt.access_token); + if (!roles.length || !roles.includes(verifiedUser.role)) { + throw new AppError("You are not authorize!!", 401); + } + req.user = verifiedUser; + next(); + } + catch (err) { + next(err); + } + }; +}; +export default auth; diff --git a/dist/app/middlewares/global_error_handler.js b/dist/app/middlewares/global_error_handler.js new file mode 100644 index 0000000..c56e5eb --- /dev/null +++ b/dist/app/middlewares/global_error_handler.js @@ -0,0 +1,47 @@ +import { ZodError } from "zod"; +import { configs } from "../configs/index.js"; +import handleZodError from "../errors/zodError.js"; +import { AppError } from "../utils/app_error.js"; +const globalErrorHandler = (err, req, res, next) => { + let statusCode = 500; + let message = "Something went wrong!"; + let errorSources = [ + { + path: "", + message: "Something went wrong", + }, + ]; + if (err instanceof ZodError) { + const simplifiedError = handleZodError(err); + statusCode = simplifiedError?.statusCode; + message = simplifiedError?.message; + errorSources = simplifiedError?.errorSources; + } + else if (err instanceof AppError) { + statusCode = err?.statusCode; + message = err.message; + errorSources = [ + { + path: "", + message: err?.message, + }, + ]; + } + else if (err instanceof Error) { + message = err.message; + errorSources = [ + { + path: "", + message: err?.message, + }, + ]; + } + res.status(statusCode).json({ + success: false, + message, + errorSources, + err, + stack: configs.env === "development" ? err?.stack : null, + }); +}; +export default globalErrorHandler; diff --git a/dist/app/middlewares/not_found_api.js b/dist/app/middlewares/not_found_api.js new file mode 100644 index 0000000..ab074e0 --- /dev/null +++ b/dist/app/middlewares/not_found_api.js @@ -0,0 +1,8 @@ +const notFound = (req, res, next) => { + res.status(404).json({ + message: 'Sorry Route is not found!! 😴😴😴', + success: false, + error: '', + }); +}; +export default notFound; diff --git a/dist/app/middlewares/request_validator.js b/dist/app/middlewares/request_validator.js new file mode 100644 index 0000000..73024a8 --- /dev/null +++ b/dist/app/middlewares/request_validator.js @@ -0,0 +1,12 @@ +const RequestValidator = (schema) => { + return async (req, res, next) => { + try { + req.body = await schema.parseAsync(req.body); + next(); + } + catch (err) { + next(err); + } + }; +}; +export default RequestValidator; diff --git a/dist/app/middlewares/uploader.js b/dist/app/middlewares/uploader.js new file mode 100644 index 0000000..da05753 --- /dev/null +++ b/dist/app/middlewares/uploader.js @@ -0,0 +1,12 @@ +import multer from "multer"; +import path from "path"; +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, path.join(process.cwd(), "uploads")); + }, + filename: function (req, file, cb) { + cb(null, file.originalname); + } +}); +const uploader = multer({ storage: storage }); +export default uploader; diff --git a/dist/app/modules/account/account.controller.js b/dist/app/modules/account/account.controller.js new file mode 100644 index 0000000..30f7321 --- /dev/null +++ b/dist/app/modules/account/account.controller.js @@ -0,0 +1,103 @@ +import { configs } from "../../configs/index.js"; +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { account_services } from "./account.service.js"; +const create_account = catchAsync(async (req, res) => { + const result = await account_services.create_account_into_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Account created successfully", + data: result, + }); +}); +const verify_account_using_otp = catchAsync(async (req, res) => { + const result = await account_services.verify_account_using_otp_into_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Otp verification successful", + data: result, + }); +}); +const verify_account_using_link = catchAsync(async (req, res) => { + const result = await account_services.verify_account_using_link_into_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Account verification successful", + data: result, + }); +}); +const login_user = catchAsync(async (req, res) => { + const result = await account_services.login_user_into_db(req); + // set access token into cookie + res.cookie("access_token", result, { + secure: configs.env === "production", + httpOnly: true, + }); + manageResponse(res, { + statusCode: 200, + success: true, + message: "User logged in successfully", + data: { + accessToken: result, + }, + }); +}); +const get_user_account = catchAsync(async (req, res) => { + const result = await account_services.get_user_account_from_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Account fetched successfully", + data: result, + }); +}); +const change_password = catchAsync(async (req, res) => { + const result = await account_services.change_password_into_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Password Change successfully", + data: result, + }); +}); +const resend_otp_and_verification_link = catchAsync(async (req, res) => { + const result = await account_services.resend_otp_and_verification_link_from_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "OTP reset successfully", + data: result, + }); +}); +const forget_password_generate_reset_token = catchAsync(async (req, res) => { + const result = await account_services.forget_password_generate_reset_token_from_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Password reset successfully", + data: result, + }); +}); +const reset_password_using_token = catchAsync(async (req, res) => { + const result = await account_services.reset_password_using_token_into_db(req); + manageResponse(res, { + statusCode: 200, + success: true, + message: "Password reset successfully", + data: result, + }); +}); +export const account_controller = { + create_account, + login_user, + get_user_account, + change_password, + verify_account_using_otp, + resend_otp_and_verification_link, + verify_account_using_link, + forget_password_generate_reset_token, + reset_password_using_token +}; diff --git a/dist/app/modules/account/account.route.js b/dist/app/modules/account/account.route.js new file mode 100644 index 0000000..7ac80a0 --- /dev/null +++ b/dist/app/modules/account/account.route.js @@ -0,0 +1,16 @@ +import { Router } from "express"; +import auth from "../../middlewares/auth.js"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { account_controller } from "./account.controller.js"; +import { account_validation } from "./account.validation.js"; +const accountRouter = Router(); +accountRouter.post("/sign-up", RequestValidator(account_validation.sign_up), account_controller.create_account); +accountRouter.post("/sign-in", RequestValidator(account_validation.sing_in), account_controller.login_user); +accountRouter.put("/verify-otp", RequestValidator(account_validation.verify_otp), account_controller.verify_account_using_otp); +accountRouter.put("/verify-link", RequestValidator(account_validation.verify_link), account_controller.verify_account_using_link); +accountRouter.get("/me", auth("USER", "ADMIN"), account_controller.get_user_account); +accountRouter.put("/change-password", auth("USER", "ADMIN"), RequestValidator(account_validation.change_password), account_controller.change_password); +accountRouter.put("/resend-otp", RequestValidator(account_validation.resend_otp), account_controller.resend_otp_and_verification_link); +accountRouter.put("/forget-password", RequestValidator(account_validation.resend_otp), account_controller.forget_password_generate_reset_token); +accountRouter.put("/reset-password", RequestValidator(account_validation.reset_pass), account_controller.reset_password_using_token); +export default accountRouter; diff --git a/dist/app/modules/account/account.service.js b/dist/app/modules/account/account.service.js new file mode 100644 index 0000000..8e55b46 --- /dev/null +++ b/dist/app/modules/account/account.service.js @@ -0,0 +1,348 @@ +import bcrypt from "bcrypt"; +import { configs } from "../../configs/index.js"; +import { prisma } from "../../lib/prisma.js"; +import { emailQueue } from "../../queues/email/email.queue.js"; +import { AppError } from "../../utils/app_error.js"; +import { jwtHelpers } from "../../utils/JWT.js"; +import sendMail from "../../utils/mail_sender.js"; +import { otpGenerator } from "../../utils/otpGenerator.js"; +const create_account_into_db = async (req) => { + const payload = req?.body; + // check account exist or not + const existingAccount = await prisma.account.findUnique({ + where: { email: payload.email }, + }); + if (existingAccount) { + throw new AppError("Email already exists", 403); + } + // hash password + const hashPassword = bcrypt.hashSync(payload.password, 10); + // create account and profile + const result = await prisma.$transaction(async (tx) => { + const account = await tx.account.create({ + data: { + email: payload.email, + password: hashPassword, + }, + }); + const profile = await tx.profile.create({ + data: { + shopName: payload.shopName, + accountId: account.id, + }, + }); + return { + account, + profile, + }; + }); + // sending otp and verification link + const newOtp = otpGenerator(); + const verificationToken = jwtHelpers.generateToken({ + email: payload.email, + accountId: result.account.id, + }, configs.jwt.verified_token, "5m"); + const verificationLink = `${configs.jwt.front_end_url}/verify/token?=${verificationToken}`; + // save otp into db + await prisma.account.update({ + where: { + email: payload.email, + }, + data: { + lastOtp: newOtp, + lastOtpSendingTime: new Date(), + }, + }); + await emailQueue.add("email-queue", { + name: payload.shopName, + otp: newOtp, + verificationLink: verificationLink, + subject: "Welcome to Quick Launch - Verification OTP", + email: payload.email, + textBody: "You can use otp or verification link for verifying your account" + }); + return null; +}; +const verify_account_using_otp_into_db = async (req) => { + const payload = req?.body; + // check account + const account = await prisma.account.findUnique({ + where: { + email: payload.email, + }, + }); + // check if account exists + if (!account) { + throw new AppError("Account not found", 404); + } + // match with last otp + const isOtpMatch = payload.otp === account.lastOtp; + if (!isOtpMatch) { + throw new AppError("Invalid OTP, Please try again!!", 401); + } + // check otp timing + const OTP_EXPIRY_TIME = 5 * 60 * 1000; // 5 minutes in ms + const isOtpExpired = account.lastOtpSendingTime + ? new Date().getTime() - new Date(account.lastOtpSendingTime).getTime() > + OTP_EXPIRY_TIME + : true; + if (isOtpExpired) { + throw new AppError("OTP Expired, Please try again!!", 401); + } + // change account status + await prisma.account.update({ + where: { + id: account.id, + }, + data: { + isAccountVerified: true, + }, + }); + // infuter user welcome email + return ""; +}; +const verify_account_using_link_into_db = async (req) => { + const token = req?.body?.token; + let decoadeToken; + try { + decoadeToken = jwtHelpers.verifyToken(token, configs.jwt.verified_token); + } + catch (error) { + if (error?.message == "invalid signature") { + throw new AppError("Invalid Token", 403); + } + else if (error?.message == "jwt expired") { + throw new AppError("Token expired, please reset again", 403); + } + } + // check account + const account = await prisma.account.findUnique({ + where: { + email: decoadeToken.email, + }, + }); + // check if account exists + if (!account) { + throw new AppError("Account not found", 404); + } + // change account status + await prisma.account.update({ + where: { + id: account.id, + }, + data: { + isAccountVerified: true, + }, + }); + // infuter user welcome email + return ""; +}; +const login_user_into_db = async (req) => { + const payload = req?.body; + const account = await prisma.account.findUnique({ + where: { + email: payload.email, + }, + }); + // check if account exists + if (!account) { + throw new AppError("Account not found", 404); + } + // checking password + const isPasswordMatch = bcrypt.compareSync(payload.password, account.password); + if (!isPasswordMatch) { + throw new AppError("Invalid password", 401); + } + // check if account is deleted + if (account.isDeleted) { + throw new AppError("Account is deleted", 401); + } + // check if account is verified + if (!account.isAccountVerified) { + throw new AppError("Account is not verified", 401); + } + // generate access + const accessToken = jwtHelpers.generateToken({ + email: account.email, + role: account.role, + accountId: account.id, + }, configs.jwt.access_token, configs.jwt.access_expires); + return accessToken; +}; +const get_user_account_from_db = async (req) => { + const user = req?.user; + const result = await prisma.account.findUnique({ + where: { + id: user?.accountId, + }, + select: { + id: true, + email: true, + role: true, + isAccountVerified: true, + isDeleted: true, + profile: true, + }, + }); + return result; +}; +const change_password_into_db = async (req) => { + const user = req?.user; + // payload + const payload = req?.body; + // check old and new password is not same + const isSamePassword = payload.oldPassword === payload.newPassword; + if (isSamePassword) { + throw new AppError("Old and new password are same, Please provide deffirent password", 404); + } + // check user validity + const isUserExist = await prisma.account.findFirst({ + where: { + email: user?.email, + }, + }); + // if account not exists + if (!isUserExist) { + throw new AppError("Account not found!!", 404); + } + // check old password + const isPasswordMatch = bcrypt.compareSync(payload.oldPassword, isUserExist.password); + if (!isPasswordMatch) { + throw new AppError("Incorrect password", 401); + } + // change password logic + const newHashPassword = bcrypt.hashSync(payload.newPassword, 10); + await prisma.account.update({ + where: { + id: isUserExist.id, + }, + data: { + password: newHashPassword, + }, + }); + // in future email notification for more sucurity + return ""; +}; +const resend_otp_and_verification_link_from_db = async (req) => { + const email = req?.body?.email; + const account = await prisma.account.findUnique({ + where: { + email, + }, + }); + // check if account exists + if (!account) { + throw new AppError("Account not found", 404); + } + // if already verified + if (account.isAccountVerified) { + throw new AppError("Account already verified", 403); + } + // make new otp and verification link + const newOtp = otpGenerator(); + const verificationToken = jwtHelpers.generateToken({ + email, + accountId: account.id, + }, configs.jwt.verified_token, "5m"); + const verificationLink = `${configs.jwt.front_end_url}/verify/token?=${verificationToken}`; + // save otp into db + await prisma.account.update({ + where: { + email, + }, + data: { + lastOtp: newOtp, + lastOtpSendingTime: new Date(), + }, + }); + await sendMail({ + to: email, + subject: "New Verification otp and link", + htmlBody: ` +

OTP ${newOtp}

+ Otp will be expire in 5 minutes + +

+ +

Or you can use Verification link

+

${verificationLink}

+ `, + textBody: "You can use otp or direct link", + }); +}; +const forget_password_generate_reset_token_from_db = async (req) => { + const email = req?.body?.email; + const account = await prisma.account.findUnique({ + where: { + email, + }, + }); + // check if account exists + if (!account) { + throw new AppError("Account not found", 404); + } + // generate forget token + const verificationToken = jwtHelpers.generateToken({ + email: email, + accountId: account.id, + }, configs.jwt.verified_token, "5m"); + const verificationLink = `${configs.jwt.front_end_url}/verify/token?=${verificationToken}`; + await sendMail({ + to: email, + subject: "Forget Password- Use this link for new password ", + htmlBody: ` +

Your Reset Link:

+

${verificationLink}

+ `, + textBody: "", + }); +}; +const reset_password_using_token_into_db = async (req) => { + const token = req?.body?.token; + const newPass = req?.body?.newPass; + let decoadeToken; + try { + decoadeToken = jwtHelpers.verifyToken(token, configs.jwt.verified_token); + } + catch (error) { + if (error?.message == "invalid signature") { + throw new AppError("Invalid Token", 403); + } + else if (error?.message == "jwt expired") { + throw new AppError("Link expired, please reset again", 403); + } + } + // check account + const account = await prisma.account.findUnique({ + where: { + email: decoadeToken.email, + }, + }); + // check if account exists + if (!account) { + throw new AppError("Account not found", 404); + } + // change account password + const newHash = bcrypt.hashSync(newPass, 10); + await prisma.account.update({ + where: { + id: account.id, + }, + data: { + password: newHash, + }, + }); + // infuter user alart for changing password + return ""; +}; +export const account_services = { + create_account_into_db, + login_user_into_db, + get_user_account_from_db, + change_password_into_db, + verify_account_using_otp_into_db, + resend_otp_and_verification_link_from_db, + verify_account_using_link_into_db, + forget_password_generate_reset_token_from_db, + reset_password_using_token_into_db +}; diff --git a/dist/app/modules/account/account.swagger.js b/dist/app/modules/account/account.swagger.js new file mode 100644 index 0000000..c756f62 --- /dev/null +++ b/dist/app/modules/account/account.swagger.js @@ -0,0 +1,187 @@ +export const accountSwaggerDocs = { + "/api/auth/sign-up": { + post: { + tags: ["account"], + summary: "Create new account", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + email: "user@gmail.com", + password: "password", + shopName: "User", + }), + }, + }, + }, + responses: { + 201: { description: "account created successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/sign-in": { + post: { + tags: ["account"], + summary: "Sign In your account", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + email: "user@gmail.com", + password: "password", + }), + }, + }, + }, + responses: { + 201: { description: "User signed in successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/verify-otp": { + put: { + tags: ["account"], + summary: "Verify OTP", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + email: "user@gmail.com", + otp: "654321", + }), + }, + }, + }, + responses: { + 201: { description: "OTP verification successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/verify-link": { + put: { + tags: ["account"], + summary: "Verify Link", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + token: "dsakfjasdkj", + }), + }, + }, + }, + responses: { + 201: { description: "Token verification successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/me": { + get: { + tags: ["account"], + summary: "Get me account", + description: "", + responses: { + 200: { description: "account fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, + "/api/auth/change-password": { + put: { + tags: ["account"], + summary: "Change Password", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + oldPassword: "123456", + newPassword: "654321", + }), + }, + }, + }, + responses: { + 201: { description: "Passwrod change successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/resend-otp": { + put: { + tags: ["account"], + summary: "Resend OTP", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + email: "user@gmail.com", + }), + }, + }, + }, + responses: { + 201: { description: "OTP resend successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/forget-password": { + put: { + tags: ["account"], + summary: "Forget Password", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + email: "user@gmail.com", + }), + }, + }, + }, + responses: { + 201: { description: "Forget password successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, + "/api/auth/reset-password": { + put: { + tags: ["account"], + summary: "Reset Password", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + token: "dkfjadskfds", + newPass: "newpass", + }), + }, + }, + }, + responses: { + 201: { description: "Password reset successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, +}; diff --git a/dist/app/modules/account/account.validation.js b/dist/app/modules/account/account.validation.js new file mode 100644 index 0000000..c761159 --- /dev/null +++ b/dist/app/modules/account/account.validation.js @@ -0,0 +1,37 @@ +import z from "zod"; +const sign_up = z.object({ + email: z.string("Email is required."), + password: z.string("Password is required."), + shopName: z.string("Full name is required."), +}); +const sing_in = z.object({ + email: z.string("Email is required."), + password: z.string("Password is required."), +}); +const change_password = z.object({ + oldPassword: z.string("Old Password is required"), + newPassword: z.string("New Password is required"), +}); +const verify_otp = z.object({ + email: z.string("Email is required"), + otp: z.string("OTP is required"), +}); +const verify_link = z.object({ + token: z.string("Token is required "), +}); +const resend_otp = z.object({ + email: z.string("Email is required"), +}); +const reset_pass = z.object({ + token: z.string("Token is required"), + newPass: z.string("Password is required"), +}); +export const account_validation = { + sign_up, + sing_in, + change_password, + verify_otp, + resend_otp, + verify_link, + reset_pass +}; diff --git a/dist/app/modules/order/order.controller.js b/dist/app/modules/order/order.controller.js new file mode 100644 index 0000000..d35964e --- /dev/null +++ b/dist/app/modules/order/order.controller.js @@ -0,0 +1,60 @@ +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { order_service } from "./order.service.js"; +const get_all_order = catchAsync(async (req, res) => { + const result = await order_service.get_all_order_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "All order fetched successfully.", + data: result, + meta: {}, + }); +}); +const get_single_order = catchAsync(async (req, res) => { + const result = await order_service.get_single_order_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "Single order fetched successfully.", + data: result, + meta: {}, + }); +}); +const create_order = catchAsync(async (req, res) => { + const result = await order_service.create_order_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "order created successfully.", + data: result, + meta: {}, + }); +}); +const update_order = catchAsync(async (req, res) => { + const result = await order_service.update_order_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "order updated successfully.", + data: result, + meta: {}, + }); +}); +const delete_order = catchAsync(async (req, res) => { + const result = await order_service.delete_order_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "order deleted successfully.", + data: result, + meta: {}, + }); +}); +export const order_controller = { + get_all_order, + get_single_order, + create_order, + update_order, + delete_order, +}; diff --git a/dist/app/modules/order/order.route.js b/dist/app/modules/order/order.route.js new file mode 100644 index 0000000..6950849 --- /dev/null +++ b/dist/app/modules/order/order.route.js @@ -0,0 +1,12 @@ +import { Router } from "express"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { order_controller } from "./order.controller.js"; +import { order_validations } from "./order.validation.js"; +import auth from "../../middlewares/auth.js"; +const router = Router(); +router.get("/", order_controller.get_all_order); +router.post("/", RequestValidator(order_validations.create_order), order_controller.create_order); +router.get("/:id", order_controller.get_single_order); +router.patch("/:id", auth("ADMIN"), RequestValidator(order_validations.update_order), order_controller.update_order); +router.delete("/:id", order_controller.delete_order); +export default router; diff --git a/dist/app/modules/order/order.service.js b/dist/app/modules/order/order.service.js new file mode 100644 index 0000000..d0f67c4 --- /dev/null +++ b/dist/app/modules/order/order.service.js @@ -0,0 +1,159 @@ +import { configs } from "../../configs/index.js"; +import { prisma } from "../../lib/prisma.js"; +import { orderEmailQueue } from "../../queues/email/order/order.email.queue.js"; +import { AppError } from "../../utils/app_error.js"; +import paginationHelper from "../../utils/pagination_helper.js"; +const get_all_order_from_db = async (req) => { + // define your own login here + const search = req.query.search; + const customerName = req.query.customerName; + const productName = req.query.productName; + // for date filter + const startDate = req.query.startDate; + const endDate = req.query.endDate; + const status = req.query.status || undefined; + const { page, limit, skip, sortBy, sortOrder } = paginationHelper(req.query); + const andCondition = []; + if (search) { + andCondition.push({ + OR: [ + { + productName: { + contains: search, + mode: "insensitive", + }, + }, + ], + }); + } + if (customerName) { + andCondition.push({ + OR: [ + { + customerName: { + contains: customerName, + mode: "insensitive", + }, + }, + ], + }); + } + if (productName) { + andCondition.push({ + OR: [ + { + productName: { + contains: productName, + mode: "insensitive", + }, + }, + ], + }); + } + if (status) { + andCondition.push({ + OR: [ + { + status: { + contains: status, + mode: "insensitive", + }, + }, + ], + }); + } + // for date filter + const dateFilter = {}; + if (startDate) { + const start = new Date(startDate); + start.setHours(0, 0, 0, 0); + dateFilter.gte = start; + } + if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + dateFilter.lte = end; + } + if (Object.keys(dateFilter).length > 0) { + andCondition.push({ + createdAt: dateFilter, + }); + } + const getAllOrders = await prisma.order.findMany({ + take: limit, + skip, + where: { + AND: andCondition, + }, + orderBy: { + [sortBy]: sortOrder, + }, + }); + const result = await prisma.order.count({ + where: { + AND: andCondition, + }, + }); + return { + data: getAllOrders, + pagination: { + total: result, + page, + limit, + totalPages: Math.ceil(result / limit), + }, + }; +}; +const get_single_order_from_db = async (req) => { + // define your own login here + const { id } = req.params; + const result = await prisma.order.findUnique({ where: { id } }); + return result; +}; +const create_order_into_db = async (req) => { + const payload = req?.body; + console.log(payload); + payload.status = "INITIATED"; + payload.paymentType = "COD"; + // nwo init order + const result = await prisma.order.create({ data: payload }); + // if email exist sent tracking link + if (payload.customerEmail) { + const trackingLink = `${configs.jwt.front_end_url}/track-order/${result.id}`; + await orderEmailQueue.add("order-email-queue", { + email: payload.customerEmail, + subject: "Order Tracking", + textBody: `Your order has been created. Track your order here: ${trackingLink}`, + htmlBody: `

Your order has been created. Track your order here: Track Order

`, + }); + } + return result; +}; +const update_order_into_db = async (req) => { + // define your own login here + const user = req.user; + console.log(user); + if (user?.role !== "ADMIN") { + throw new AppError("You are not authorized to perform this action", 403); + } + const { id } = req.params; + const isProductExist = await prisma.order.findUnique({ where: { id } }); + if (!isProductExist) { + throw new AppError("Order is not found", 404); + } + const result = await prisma.order.update({ where: { id }, data: req.body }); + return result; +}; +const delete_order_from_db = async (req) => { + // define your own login here + const { id } = req.params; + const result = await prisma.order.delete({ where: { id } }); + return result; +}; +export const order_service = { + get_all_order_from_db, + get_single_order_from_db, + create_order_into_db, + update_order_into_db, + delete_order_from_db, +}; diff --git a/dist/app/modules/order/order.swagger.js b/dist/app/modules/order/order.swagger.js new file mode 100644 index 0000000..3567dd4 --- /dev/null +++ b/dist/app/modules/order/order.swagger.js @@ -0,0 +1,161 @@ +export const orderSwaggerDocs = { + "/api/order": { + post: { + tags: ["order"], + summary: "Create new order", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + shopAccountId: "", + productPrice: 1500, + productQuantity: 2, + productName: "Wireless Mouse", + customerName: "Rahim Uddin", + customerPhone: "+8801712345678", + customerEmail: "softvence.abumahid@gmail.com", + customerAddress: "Rangpur, Bangladesh", + customerNote: "Please deliver between 3-5 PM", + }), + }, + }, + }, + responses: { + 201: { description: "order created successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + get: { + tags: ["order"], + summary: "Get all order", + description: "", + parameters: [ + { + name: "page", + in: "query", + required: false, + schema: { type: "number" }, + }, + { + name: "limit", + in: "query", + required: false, + schema: { type: "number" }, + }, + { + name: "search", + in: "query", + required: false, + schema: { type: "string" }, + }, + { + name: "customerName", + in: "query", + required: false, + schema: { type: "string" }, + }, + { + name: "productName", + in: "query", + required: false, + schema: { type: "string" }, + }, + { + name: "status", + in: "query", + required: false, + schema: { type: "string" }, + }, + { + name: "date", + in: "query", + required: false, + schema: { type: "string" }, + }, + { + name: "startDate", + in: "query", + required: false, + schema: { type: "string", format: "date" }, + example: "2026-04-01", + }, + { + name: "endDate", + in: "query", + required: false, + schema: { type: "string", format: "date" }, + example: "2026-04-31", + }, + ], + responses: { + 200: { description: "order fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, + "/api/order/{id}": { + get: { + tags: ["order"], + summary: "Get single order", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "order fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + patch: { + tags: ["order"], + summary: "Update order", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + status: "INITIATED", + }), // put your request body + }, + }, + }, + responses: { + 200: { description: "order updated successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + delete: { + tags: ["order"], + summary: "Delete order", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "order delete successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, +}; diff --git a/dist/app/modules/order/order.validation.js b/dist/app/modules/order/order.validation.js new file mode 100644 index 0000000..0debae2 --- /dev/null +++ b/dist/app/modules/order/order.validation.js @@ -0,0 +1,19 @@ +import { z } from "zod"; +const create_order = z.object({ + shopAccountId: z.string(), + productPrice: z.number(), + productQuantity: z.number(), + productName: z.string(), + customerName: z.string(), + customerPhone: z.string(), + customerEmail: z.string().optional(), + customerAddress: z.string(), + customerNote: z.string().optional() +}); +const update_order = z.object({ + status: z.string().optional() +}); +export const order_validations = { + create_order, + update_order, +}; diff --git a/dist/app/modules/plan/plan.controller.js b/dist/app/modules/plan/plan.controller.js new file mode 100644 index 0000000..b03bf85 --- /dev/null +++ b/dist/app/modules/plan/plan.controller.js @@ -0,0 +1,60 @@ +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { plan_service } from "./plan.service.js"; +const get_all_plan = catchAsync(async (req, res) => { + const result = await plan_service.get_all_plan_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "All plan fetched successfully.", + data: result, + meta: {}, + }); +}); +const get_single_plan = catchAsync(async (req, res) => { + const result = await plan_service.get_single_plan_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "Single plan fetched successfully.", + data: result, + meta: {}, + }); +}); +const create_plan = catchAsync(async (req, res) => { + const result = await plan_service.create_plan_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "plan created successfully.", + data: result, + meta: {}, + }); +}); +const update_plan = catchAsync(async (req, res) => { + const result = await plan_service.update_plan_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "plan updated successfully.", + data: result, + meta: {}, + }); +}); +const delete_plan = catchAsync(async (req, res) => { + const result = await plan_service.delete_plan_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "plan deleted successfully.", + data: result, + meta: {}, + }); +}); +export const plan_controller = { + get_all_plan, + get_single_plan, + create_plan, + update_plan, + delete_plan, +}; diff --git a/dist/app/modules/plan/plan.route.js b/dist/app/modules/plan/plan.route.js new file mode 100644 index 0000000..860b954 --- /dev/null +++ b/dist/app/modules/plan/plan.route.js @@ -0,0 +1,11 @@ +import { Router } from "express"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { plan_controller } from "./plan.controller.js"; +import { plan_validations } from "./plan.validation.js"; +const router = Router(); +router.get("/", plan_controller.get_all_plan); +router.post("/", RequestValidator(plan_validations.create_plan), plan_controller.create_plan); +router.get("/:id", plan_controller.get_single_plan); +router.patch("/:id", RequestValidator(plan_validations.update_plan), plan_controller.update_plan); +router.delete("/:id", plan_controller.delete_plan); +export default router; diff --git a/dist/app/modules/plan/plan.service.js b/dist/app/modules/plan/plan.service.js new file mode 100644 index 0000000..f363cf2 --- /dev/null +++ b/dist/app/modules/plan/plan.service.js @@ -0,0 +1,68 @@ +import { prisma } from "../../lib/prisma.js"; +import { AppError } from "../../utils/app_error.js"; +const get_all_plan_from_db = async (req) => { + // define your own login here + const result = await prisma.plan.findMany(); + return result; +}; +const get_single_plan_from_db = async (req) => { + // define your own login here + const { id } = req.params; + const result = await prisma.plan.findUnique({ + where: { + id: id + } + }); + return result; +}; +const create_plan_into_db = async (req) => { + // define your own login here + const user = req.user; + if (user?.role !== "ADMIN") { + throw new AppError("You don’t have permission to create plan information.!!!", 401); + } + const result = await prisma.plan.create({ data: req.body }); + return result; +}; +const update_plan_into_db = async (req) => { + // define your own login here + const { id } = req.params; + const user = req.user; + if (user?.role !== "ADMIN") { + throw new AppError("You don’t have permission to update plan information.!!!", 401); + } + const isPlanExist = await prisma.plan.findFirst({ + where: { + id: id + } + }); + if (!isPlanExist) { + throw new AppError("The plan is not available!!!", 404); + } + const result = await prisma.plan.update({ + where: { + id: isPlanExist.id + }, + data: { + ...req.body + } + }); + return result; +}; +const delete_plan_from_db = async (req) => { + // define your own login here + const { id } = req.params; + const user = req.user; + if (user?.role !== "ADMIN") { + throw new AppError("You don’t have permission to delete plan information.!!!", 401); + } + const result = await prisma.plan.delete({ where: { id: id } }); + return result; +}; +export const plan_service = { + get_all_plan_from_db, + get_single_plan_from_db, + create_plan_into_db, + update_plan_into_db, + delete_plan_from_db, +}; diff --git a/dist/app/modules/plan/plan.swagger.js b/dist/app/modules/plan/plan.swagger.js new file mode 100644 index 0000000..eb85a64 --- /dev/null +++ b/dist/app/modules/plan/plan.swagger.js @@ -0,0 +1,125 @@ +export const planSwaggerDocs = { + "/api/plan": { + post: { + tags: ["plan"], + summary: "Create new plan", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + "planName": "PRO Plan", + "price": 12, + "planType": "PRO", + "planDesc": "The plan is only for pro users", + "planFeatures": { + "storage": "10GB", + "projects": 5, + "support": "Email Support" + } + }), // put your request body + }, + }, + }, + responses: { + 201: { description: "plan created successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + get: { + tags: ["plan"], + summary: "Get all plan", + description: "", + parameters: [ + { + name: "page", + in: "query", + required: false, + schema: { type: "number" }, + }, + { + name: "limit", + in: "query", + required: false, + schema: { type: "number" }, + }, + ], + responses: { + 200: { description: "plan fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, + "/api/plan/{id}": { + get: { + tags: ["plan"], + summary: "Get single plan", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "plan fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + patch: { + tags: ["plan"], + summary: "Update plan", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + "planName": "PRO Plan", + "price": 12, + "planType": "PRO", + "planDesc": "The plan is only for pro users", + "planFeatures": { + "storage": "10GB", + "projects": 5, + "support": "Email Support" + } + }), // put your request body + }, + }, + }, + responses: { + 200: { description: "plan updated successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + delete: { + tags: ["plan"], + summary: "Delete plan", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "plan delete successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, +}; diff --git a/dist/app/modules/plan/plan.validation.js b/dist/app/modules/plan/plan.validation.js new file mode 100644 index 0000000..d38d612 --- /dev/null +++ b/dist/app/modules/plan/plan.validation.js @@ -0,0 +1,33 @@ +import { z } from "zod"; +const create_plan = z.object({ + planName: z.string("Enter the plan name..."), + price: z.number("Enter the plan price..."), + planType: z.enum(["FREE", "STANDARD", "PRO"]), + planDesc: z.string("Enter the plan description..."), + planFeatures: z.union([ + z.string(), + z.number(), + z.boolean(), + z.null(), + z.array(z.any()), + z.record(z.string(), z.any()) + ]) +}); +const update_plan = z.object({ + planName: z.string(), + price: z.number(), + planType: z.enum(["FREE", "STANDARD", "PRO"]), + planDesc: z.string().optional(), + planFeatures: z.union([ + z.string(), + z.number(), + z.boolean(), + z.null(), + z.array(z.any()), + z.record(z.string(), z.any()) + ]) +}); +export const plan_validations = { + create_plan, + update_plan, +}; diff --git a/dist/app/modules/profile/profile.controller.js b/dist/app/modules/profile/profile.controller.js new file mode 100644 index 0000000..e6abe89 --- /dev/null +++ b/dist/app/modules/profile/profile.controller.js @@ -0,0 +1,15 @@ +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { profile_service } from "./profile.service.js"; +const update_profile = catchAsync(async (req, res) => { + const result = await profile_service.update_profile_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "profile updated successfully.", + data: result, + }); +}); +export const profile_controller = { + update_profile, +}; diff --git a/dist/app/modules/profile/profile.route.js b/dist/app/modules/profile/profile.route.js new file mode 100644 index 0000000..5a408a2 --- /dev/null +++ b/dist/app/modules/profile/profile.route.js @@ -0,0 +1,12 @@ +import { Router } from "express"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { profile_controller } from "./profile.controller.js"; +import { profile_validations } from "./profile.validation.js"; +import auth from "../../middlewares/auth.js"; +import uploader from "../../middlewares/uploader.js"; +const router = Router(); +router.patch("/", auth("USER"), uploader.single("file"), (req, res, next) => { + req.body = JSON.parse(req?.body?.data); + next(); +}, RequestValidator(profile_validations.update_profile), profile_controller.update_profile); +export default router; diff --git a/dist/app/modules/profile/profile.service.js b/dist/app/modules/profile/profile.service.js new file mode 100644 index 0000000..17ca7e6 --- /dev/null +++ b/dist/app/modules/profile/profile.service.js @@ -0,0 +1,22 @@ +import uploadCloud from "../../utils/cloudinary.js"; +import { prisma } from "../../lib/prisma.js"; +const update_profile_into_db = async (req) => { + const user = req?.user; + const payload = req?.body; + const file = req?.file; + // check file and upload to cloud + if (file) { + const cloudRes = await uploadCloud(file); + payload.profilePhoto = cloudRes?.secure_url; + } + const result = await prisma.profile.update({ + where: { + accountId: user.accountId, + }, + data: payload, + }); + return result; +}; +export const profile_service = { + update_profile_into_db, +}; diff --git a/dist/app/modules/profile/profile.swagger.js b/dist/app/modules/profile/profile.swagger.js new file mode 100644 index 0000000..bdfe3c2 --- /dev/null +++ b/dist/app/modules/profile/profile.swagger.js @@ -0,0 +1,34 @@ +export const profileSwaggerDocs = { + "/api/profile": { + patch: { + tags: ["profile"], + summary: "Update profile", + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + data: { + type: "object", + properties: { + fullName: { type: "string" }, + }, + }, + file: { + type: "string", + format: "binary", + }, + }, + }, + }, + }, + }, + responses: { + 200: { description: "profile updated successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + }, +}; diff --git a/dist/app/modules/profile/profile.validation.js b/dist/app/modules/profile/profile.validation.js new file mode 100644 index 0000000..bb0240a --- /dev/null +++ b/dist/app/modules/profile/profile.validation.js @@ -0,0 +1,7 @@ +import { z } from "zod"; +const update_profile = z.object({ + fullName: z.string().optional(), +}); +export const profile_validations = { + update_profile, +}; diff --git a/dist/app/modules/support/support.controller.js b/dist/app/modules/support/support.controller.js new file mode 100644 index 0000000..deedd95 --- /dev/null +++ b/dist/app/modules/support/support.controller.js @@ -0,0 +1,80 @@ +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { support_service } from "./support.service.js"; +const createSupport = catchAsync(async (req, res) => { + const id = req?.user?.accountId; + const data = { + ...req.body, + storeAccountId: id + }; + const result = await support_service.createSupportIntoDB(data); + manageResponse(res, { + success: true, + statusCode: 200, + message: "support created successfully.", + data: result, + meta: {}, + }); +}); +const getAllSupport = catchAsync(async (req, res) => { + const role = req?.user?.role; + const id = req?.user?.accountId; + const search = req?.query?.search; + const type = req?.query?.type; + const status = req?.query?.status; + const result = await support_service.getAllSupportFromDB(id, role, search, type, status); + manageResponse(res, { + success: true, + statusCode: 200, + message: "All support fetched successfully.", + data: result, + meta: {}, + }); +}); +const get_single_support = catchAsync(async (req, res) => { + const { id } = req.params; + const userId = req?.user?.accountId; + const role = req?.user?.role; + const result = await support_service.getSingleSupportFromDB(id, userId, role); + manageResponse(res, { + success: true, + statusCode: 200, + message: "Single support fetched successfully.", + data: result, + meta: {}, + }); +}); +const update_support = catchAsync(async (req, res) => { + const { id } = req.params; + const userId = req?.user?.accountId; + const role = req?.user?.role; + const data = req.body; + const result = await support_service.updateSupportIntoDB(id, userId, role, data); + manageResponse(res, { + success: true, + statusCode: 200, + message: "support updated successfully.", + data: result, + meta: {}, + }); +}); +const delete_support = catchAsync(async (req, res) => { + const { id } = req.params; + const userId = req?.user?.accountId; + const role = req?.user?.role; + const result = await support_service.deleteSupportFromDB(id, userId, role); + manageResponse(res, { + success: true, + statusCode: 200, + message: "support deleted successfully.", + data: result, + meta: {}, + }); +}); +export const support_controller = { + createSupport, + getAllSupport, + get_single_support, + update_support, + delete_support, +}; diff --git a/dist/app/modules/support/support.route.js b/dist/app/modules/support/support.route.js new file mode 100644 index 0000000..6babe9b --- /dev/null +++ b/dist/app/modules/support/support.route.js @@ -0,0 +1,12 @@ +import { Router } from "express"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { support_controller } from "./support.controller.js"; +import { support_validations } from "./support.validation.js"; +import auth from "../../middlewares/auth.js"; +const router = Router(); +router.get("/", auth("ADMIN", "USER"), support_controller.getAllSupport); +router.post("/", auth("ADMIN", "USER"), RequestValidator(support_validations.create_support), support_controller.createSupport); +router.get("/:id", auth("ADMIN", "USER"), support_controller.get_single_support); +router.patch("/:id", auth("ADMIN", "USER"), RequestValidator(support_validations.update_support), support_controller.update_support); +router.delete("/:id", auth("ADMIN", "USER"), support_controller.delete_support); +export default router; diff --git a/dist/app/modules/support/support.service.js b/dist/app/modules/support/support.service.js new file mode 100644 index 0000000..0825aaa --- /dev/null +++ b/dist/app/modules/support/support.service.js @@ -0,0 +1,100 @@ +import { prisma } from "../../lib/prisma.js"; +import { AppError } from "../../utils/app_error.js"; +const createSupportIntoDB = async (payload) => { + const result = await prisma.support.create({ data: payload }); + return result; +}; +const getAllSupportFromDB = async (user_id, role, search, type, status) => { + const andCondition = []; + if (search) { + andCondition.push({ + OR: [ + { + issueName: { + contains: search, + mode: "insensitive", + }, + }, + { + description: { + contains: search, + mode: "insensitive", + }, + }, + ], + }); + } + if (type) { + andCondition.push({ + type: type, + }); + } + if (status) { + andCondition.push({ + status: status, + }); + } + if (role !== "ADMIN") { + andCondition.push({ + storeAccountId: user_id, + }); + } + const whereCondition = andCondition.length > 0 ? { AND: andCondition } : {}; + const result = await prisma.support.findMany({ + where: whereCondition, + orderBy: { + createdAt: "desc", + }, + }); + return result; +}; +const getSingleSupportFromDB = async (id, userId, role) => { + const support = await prisma.support.findUnique({ + where: { id }, + }); + if (!support) { + throw new AppError("Support not found", 404); + } + if (role !== "ADMIN" && support.storeAccountId !== userId) { + throw new AppError("You are not authorized", 403); + } + return support; +}; +const updateSupportIntoDB = async (id, userId, role, payload) => { + const support = await prisma.support.findUnique({ + where: { id }, + }); + if (!support) { + throw new AppError("Support not found", 404); + } + if (role !== "ADMIN" && support.storeAccountId !== userId) { + throw new AppError("You are not authorized", 403); + } + const result = await prisma.support.update({ + where: { id }, + data: payload, + }); + return result; +}; +const deleteSupportFromDB = async (id, userId, role) => { + const support = await prisma.support.findUnique({ + where: { id }, + }); + if (!support) { + throw new AppError("Support not found", 404); + } + if (role !== "ADMIN" && support.storeAccountId !== userId) { + throw new AppError("You are not authorized", 403); + } + const result = await prisma.support.delete({ + where: { id } + }); + return result; +}; +export const support_service = { + createSupportIntoDB, + getAllSupportFromDB, + getSingleSupportFromDB, + updateSupportIntoDB, + deleteSupportFromDB, +}; diff --git a/dist/app/modules/support/support.swagger.js b/dist/app/modules/support/support.swagger.js new file mode 100644 index 0000000..cd29843 --- /dev/null +++ b/dist/app/modules/support/support.swagger.js @@ -0,0 +1,109 @@ +export const supportSwaggerDocs = { + "/api/support": { + post: { + tags: ["support"], + summary: "Create new support", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + "issueName": "Your issue name", + "description": "Issue description", + "type": "Issue Type" + }), // put your request body + }, + }, + }, + responses: { + 201: { description: "support created successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + get: { + tags: ["support"], + summary: "Get all support", + description: "", + parameters: [ + { + name: "page", + in: "query", + required: false, + schema: { type: "number" }, + }, + { + name: "limit", + in: "query", + required: false, + schema: { type: "number" }, + }, + ], + responses: { + 200: { description: "support fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, + "/api/support/{id}": { + get: { + tags: ["support"], + summary: "Get single support", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "support fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + patch: { + tags: ["support"], + summary: "Update support", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({}), // put your request body + }, + }, + }, + responses: { + 200: { description: "support updated successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + delete: { + tags: ["support"], + summary: "Delete support", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "support delete successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, +}; diff --git a/dist/app/modules/support/support.validation.js b/dist/app/modules/support/support.validation.js new file mode 100644 index 0000000..4ccbd97 --- /dev/null +++ b/dist/app/modules/support/support.validation.js @@ -0,0 +1,28 @@ +import { z } from "zod"; +const create_support = z.object({ + issueName: z.string().min(1, "issueName is required"), + description: z.string().min(1, "description is required"), + type: z.enum([ + "TECHNICAL", + "BILLING", + "DOMAIN", + "TEMPLATE", + "PAYMENT", + "ACCOUNT", + "FEATURE_REQUEST", + "BUG", + "OTHER", + ]), +}); +const update_support = z.object({ + issueName: z.string().optional(), + description: z.string().optional(), + type: z.enum(["BUG", "PAYMENT", "ACCOUNT", "OTHER"]).optional(), + status: z.enum(["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"]).optional(), + resolvedBy: z.string().optional(), + resolvedAt: z.coerce.date().optional(), +}); +export const support_validations = { + create_support, + update_support, +}; diff --git a/dist/app/queues/connection.js b/dist/app/queues/connection.js new file mode 100644 index 0000000..d7a4fc7 --- /dev/null +++ b/dist/app/queues/connection.js @@ -0,0 +1,4 @@ +export const redisConnection = { + host: "127.0.0.1", + port: 6379, +}; diff --git a/dist/app/queues/email/email.processor.js b/dist/app/queues/email/email.processor.js new file mode 100644 index 0000000..e7ddd44 --- /dev/null +++ b/dist/app/queues/email/email.processor.js @@ -0,0 +1,14 @@ +import { otpTemplate } from "../../templates/otpTemplate.js"; +import sendMail from "../../utils/mail_sender.js"; +// email.processor.ts +export const emailProcessor = async (job) => { + const payload = job.data; + await sendMail({ + to: payload.email, + subject: payload.subject, + htmlBody: otpTemplate(payload), + textBody: payload.textBody || "", + name: payload.name, + }); + console.log("Sending email job complete:", job.id); +}; diff --git a/dist/app/queues/email/email.queue.js b/dist/app/queues/email/email.queue.js new file mode 100644 index 0000000..afb2b7c --- /dev/null +++ b/dist/app/queues/email/email.queue.js @@ -0,0 +1,6 @@ +// email.queue.ts +import { Queue } from "bullmq"; +import { redisConnection } from "../connection.js"; +export const emailQueue = new Queue("email-queue", { + connection: redisConnection, +}); diff --git a/dist/app/queues/email/email.worker.js b/dist/app/queues/email/email.worker.js new file mode 100644 index 0000000..2130b4d --- /dev/null +++ b/dist/app/queues/email/email.worker.js @@ -0,0 +1,7 @@ +// email.worker.ts +import { Worker } from "bullmq"; +import { redisConnection } from "../connection.js"; +import { emailProcessor } from "./email.processor.js"; +export const emailWorker = new Worker("email-queue", async (job) => emailProcessor(job), { + connection: redisConnection, +}); diff --git a/dist/app/queues/email/order/order.email.processor.js b/dist/app/queues/email/order/order.email.processor.js new file mode 100644 index 0000000..8b0834f --- /dev/null +++ b/dist/app/queues/email/order/order.email.processor.js @@ -0,0 +1,12 @@ +import sendMail from "../../../utils/mail_sender.js"; +// email.processor.ts +export const orderEmailProcessor = async (job) => { + const payload = job.data; + await sendMail({ + to: payload.email, + subject: payload.subject, + htmlBody: payload.htmlBody, + textBody: payload.textBody, + }); + console.log("Sending email job complete:", job.id); +}; diff --git a/dist/app/queues/email/order/order.email.queue.js b/dist/app/queues/email/order/order.email.queue.js new file mode 100644 index 0000000..5e8bd36 --- /dev/null +++ b/dist/app/queues/email/order/order.email.queue.js @@ -0,0 +1,5 @@ +import { Queue } from "bullmq"; +import { redisConnection } from "../../connection.js"; +export const orderEmailQueue = new Queue("order-email-queue", { + connection: redisConnection, +}); diff --git a/dist/app/queues/email/order/order.email.worker.js b/dist/app/queues/email/order/order.email.worker.js new file mode 100644 index 0000000..4dbd0a1 --- /dev/null +++ b/dist/app/queues/email/order/order.email.worker.js @@ -0,0 +1,7 @@ +// email.worker.ts +import { Worker } from "bullmq"; +import { redisConnection } from "../../connection.js"; +import { orderEmailProcessor } from "./order.email.processor.js"; +export const emailWorker = new Worker("order-email-queue", async (job) => orderEmailProcessor(job), { + connection: redisConnection, +}); diff --git a/dist/app/queues/worker.js b/dist/app/queues/worker.js new file mode 100644 index 0000000..5801c24 --- /dev/null +++ b/dist/app/queues/worker.js @@ -0,0 +1,3 @@ +import "./email/email.worker.js"; +import "./email/order/order.email.worker.js"; +console.log("Workers running..."); diff --git a/dist/app/templates/otpTemplate.js b/dist/app/templates/otpTemplate.js new file mode 100644 index 0000000..e6902d6 --- /dev/null +++ b/dist/app/templates/otpTemplate.js @@ -0,0 +1,80 @@ +export const otpTemplate = (payload) => { + return ` +
+ + + + +
+

+ Use the following One-Time Password (OTP) to complete your + verification: +

+ + +
+ + ${payload.otp} + +
+ +

+ This OTP will expire in 5 minutes. +

+ + +
+ + +

+ Or verify using this link: +

+ +

+ + ${payload.verificationLink} + +

+
+
+ `; +}; diff --git a/dist/app/utils/JWT.js b/dist/app/utils/JWT.js new file mode 100644 index 0000000..28765e5 --- /dev/null +++ b/dist/app/utils/JWT.js @@ -0,0 +1,15 @@ +import jwt from "jsonwebtoken"; +const generateToken = (payload, secret, expiresIn) => { + const token = jwt.sign(payload, secret, { + algorithm: "HS256", + expiresIn: expiresIn, + }); + return token; +}; +const verifyToken = (token, secret) => { + return jwt.verify(token, secret); +}; +export const jwtHelpers = { + generateToken, + verifyToken, +}; diff --git a/dist/app/utils/app_error.js b/dist/app/utils/app_error.js new file mode 100644 index 0000000..fd82542 --- /dev/null +++ b/dist/app/utils/app_error.js @@ -0,0 +1,13 @@ +export class AppError extends Error { + statusCode; + constructor(message, statusCode, stack = '') { + super(message); + this.statusCode = statusCode; + if (stack) { + this.stack = stack; + } + else { + Error.captureStackTrace(this, this.constructor); + } + } +} diff --git a/dist/app/utils/catch_async.js b/dist/app/utils/catch_async.js new file mode 100644 index 0000000..15940c5 --- /dev/null +++ b/dist/app/utils/catch_async.js @@ -0,0 +1,11 @@ +const catchAsync = (fn) => { + return async (req, res, next) => { + try { + await fn(req, res, next); + } + catch (error) { + next(error); + } + }; +}; +export default catchAsync; diff --git a/dist/app/utils/cloudinary.js b/dist/app/utils/cloudinary.js new file mode 100644 index 0000000..62ec6e1 --- /dev/null +++ b/dist/app/utils/cloudinary.js @@ -0,0 +1,23 @@ +import { v2 as cloudinary } from 'cloudinary'; +import fs from 'fs'; +import { configs } from '../configs/index.js'; +// Configuration +cloudinary.config({ + cloud_name: configs.cloudinary.cloud_name, + api_key: configs.cloudinary.cloud_api_key, + api_secret: configs.cloudinary.cloud_api_secret, +}); +const uploadCloud = async (file) => { + return new Promise((resolve, reject) => { + cloudinary.uploader.upload(file.path, (error, result) => { + fs.unlinkSync(file.path); + if (error) { + reject(error); + } + else { + resolve(result); + } + }); + }); +}; +export default uploadCloud; diff --git a/dist/app/utils/mail_sender.js b/dist/app/utils/mail_sender.js new file mode 100644 index 0000000..c8b321f --- /dev/null +++ b/dist/app/utils/mail_sender.js @@ -0,0 +1,113 @@ +import nodemailer from 'nodemailer'; +import { configs } from '../configs/index.js'; +const transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, // true for 465, false for other ports + auth: { + user: configs.email.app_email, + pass: configs.email.app_password, + }, +}); +// ✅ Email Sender Function +const sendMail = async (payload) => { + const info = await transporter.sendMail({ + from: 'info@digitalcreditai.com', + to: payload.to, + subject: payload.subject, + text: payload.textBody, + html: ` + + + + + Welcome Email + + + + + +
+
+

+ Hi ${payload?.name || ""}, +

+ + ${payload?.htmlBody} + +
+ Quick Launch + +

The Support Team

+

Quick Launch

+
+
+

+ This is an automated message — please do not reply to this email. +
+ If you need assistance, feel free to contact our support team. +

+ Thank you for choosing us! +

+ +
+
+ © 2026 to {{year}} Quick Launch. All rights reserved. +
+
+ + + + + `, + }); + return info; +}; +export default sendMail; diff --git a/dist/app/utils/manage_response.js b/dist/app/utils/manage_response.js new file mode 100644 index 0000000..d33865a --- /dev/null +++ b/dist/app/utils/manage_response.js @@ -0,0 +1,9 @@ +const manageResponse = (res, payload) => { + res.status(payload.statusCode).json({ + success: payload.success, + message: payload.message, + data: payload.data || undefined || null, + meta: payload.meta || undefined || null + }); +}; +export default manageResponse; diff --git a/dist/app/utils/otpGenerator.js b/dist/app/utils/otpGenerator.js new file mode 100644 index 0000000..842096d --- /dev/null +++ b/dist/app/utils/otpGenerator.js @@ -0,0 +1,23 @@ +export const otpGenerator = () => { + const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const lower = "abcdefghijklmnopqrstuvwxyz"; + const numbers = "0123456789"; + const symbols = "@#$%&*!?"; + const allChars = upper + lower + numbers + symbols; + let otp = ""; + // Ensure at least one character from each set + otp += upper[Math.floor(Math.random() * upper.length)]; + otp += lower[Math.floor(Math.random() * lower.length)]; + otp += numbers[Math.floor(Math.random() * numbers.length)]; + otp += symbols[Math.floor(Math.random() * symbols.length)]; + // Fill the rest randomly + for (let i = otp.length; i < 6; i++) { + otp += allChars[Math.floor(Math.random() * allChars.length)]; + } + // Shuffle to remove predictable order + otp = otp + .split("") + .sort(() => Math.random() - 0.5) + .join(""); + return otp; +}; diff --git a/dist/app/utils/pagination_helper.js b/dist/app/utils/pagination_helper.js new file mode 100644 index 0000000..63a8a8f --- /dev/null +++ b/dist/app/utils/pagination_helper.js @@ -0,0 +1,15 @@ +const paginationHelper = (options) => { + const page = Number(options?.page) || 1; + const limit = Number(options?.limit) || 10; + const skip = (page - 1) * limit; + const sortBy = options?.sortBy || "createdAt"; + const sortOrder = options?.sortOrder || "desc"; + return { + page, + limit, + skip, + sortBy, + sortOrder + }; +}; +export default paginationHelper; diff --git a/dist/routes.js b/dist/routes.js new file mode 100644 index 0000000..5185c65 --- /dev/null +++ b/dist/routes.js @@ -0,0 +1,16 @@ +import { Router } from "express"; +import accountRouter from "./app/modules/account/account.route.js"; +import orderRoute from "./app/modules/order/order.route.js"; +import planRoute from "./app/modules/plan/plan.route.js"; +import profileRoute from "./app/modules/profile/profile.route.js"; +import supportRoute from "./app/modules/support/support.route.js"; +const appRouter = Router(); +const moduleRoutes = [ + { path: "/order", route: orderRoute }, + { path: "/support", route: supportRoute }, + { path: "/plan", route: planRoute }, + { path: "/profile", route: profileRoute }, + { path: "/auth", route: accountRouter }, +]; +moduleRoutes.forEach((route) => appRouter.use(route.path, route.route)); +export default appRouter; diff --git a/dist/server.js b/dist/server.js new file mode 100644 index 0000000..f1110e5 --- /dev/null +++ b/dist/server.js @@ -0,0 +1,19 @@ +import app from "./app.js"; +import { configs } from "./app/configs/index.js"; +import { prisma } from "./app/lib/prisma.js"; +import "./app/queues/worker.js"; +async function main() { + try { + app.listen(configs.port, async () => { + await prisma.$connect(); + console.log(`Server is running on port ${configs.port}`); + }); + } + catch (error) { + console.error("Error starting server:", error); + } +} +main().catch((error) => { + console.error("Error in main function:", error); + process.exit(1); +}); diff --git a/dist/swaggerOptions.js b/dist/swaggerOptions.js new file mode 100644 index 0000000..164612c --- /dev/null +++ b/dist/swaggerOptions.js @@ -0,0 +1,48 @@ +import { fileURLToPath } from "node:url"; +import path from "path"; +import { configs } from "./app/configs/index.js"; +import { accountSwaggerDocs } from "./app/modules/account/account.swagger.js"; +import { orderSwaggerDocs } from "./app/modules/order/order.swagger.js"; +import { planSwaggerDocs } from "./app/modules/plan/plan.swagger.js"; +import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger.js"; +import { supportSwaggerDocs } from "./app/modules/support/support.swagger.js"; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +export const swaggerOptions = { + definition: { + openapi: "3.0.0", + info: { + title: "API Doc - Build with exp-node-server", + version: "1.0.0", + description: "Express + Prisma API with auto-generated Swagger docs", + }, + paths: { + ...accountSwaggerDocs, + ...planSwaggerDocs, + ...orderSwaggerDocs, + ...profileSwaggerDocs, + ...supportSwaggerDocs, + }, + servers: configs.env === "production" + ? [{ url: "https://live-url.com" }, { url: "http://localhost:5000" }] + : [{ url: "http://localhost:5000" }, { url: "https://live-url.com" }], + components: { + securitySchemes: { + AuthorizationToken: { + type: "apiKey", + in: "header", + name: "Authorization", + description: "Put your accessToken here ", + }, + }, + }, + security: [ + { + AuthorizationToken: [], + }, + ], + }, + apis: [ + path.join(__dirname, configs.env === "production" ? "./**/*.js" : "./**/*.ts"), + ], +}; diff --git a/package.json b/package.json index 8d527f7..6326e8f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "pg": "^8.20.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", + "typescript": "^6.0.3", "zod": "^4.1.12" }, "devDependencies": { diff --git a/prisma/schema/schema.prisma b/prisma/schema/schema.prisma index 2500a9d..6eb2dfc 100644 --- a/prisma/schema/schema.prisma +++ b/prisma/schema/schema.prisma @@ -1,6 +1,5 @@ generator client { - provider = "prisma-client" - output = "../generated/prisma" + provider = "prisma-client-js" } datasource db { diff --git a/src/app.ts b/src/app.ts index 0dbfa9f..7e60569 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,10 +3,10 @@ import cors from 'cors'; import express, { Request, Response } from 'express'; import swaggerJSDoc from 'swagger-jsdoc'; import swaggerUi from "swagger-ui-express"; -import globalErrorHandler from './app/middlewares/global_error_handler'; -import notFound from './app/middlewares/not_found_api'; -import appRouter from './routes'; -import { swaggerOptions } from './swaggerOptions'; +import globalErrorHandler from './app/middlewares/global_error_handler.js'; +import notFound from './app/middlewares/not_found_api.js'; +import appRouter from './routes.js'; +import { swaggerOptions } from './swaggerOptions.js'; // define app const app = express() diff --git a/src/app/configs/index.ts b/src/app/configs/index.ts index 54e0554..1553fd2 100644 --- a/src/app/configs/index.ts +++ b/src/app/configs/index.ts @@ -23,4 +23,5 @@ export const configs = { cloud_api_key: process.env.CLOUD_API_KEY, cloud_api_secret: process.env.CLOUD_API_SECRET, }, + redis_url: process.env.REDIS_URL, }; diff --git a/src/app/errors/zodError.ts b/src/app/errors/zodError.ts index bc221a3..f97f1d7 100644 --- a/src/app/errors/zodError.ts +++ b/src/app/errors/zodError.ts @@ -1,5 +1,5 @@ import { ZodError, ZodIssue } from 'zod' -import { TErrorSources, TGenericErrorResponse } from '../types/error' +import { TErrorSources, TGenericErrorResponse } from '../types/error.js' const handleZodError = (err: ZodError): TGenericErrorResponse => { const errorSources: TErrorSources = err.issues.map((issue: ZodIssue) => { diff --git a/src/app/lib/prisma.ts b/src/app/lib/prisma.ts index ab78aa6..8a9d118 100644 --- a/src/app/lib/prisma.ts +++ b/src/app/lib/prisma.ts @@ -1,6 +1,7 @@ import { PrismaPg } from "@prisma/adapter-pg"; +import pkg from "@prisma/client"; import "dotenv/config"; -import { PrismaClient } from "../../../prisma/generated/prisma/client"; +const { PrismaClient } = pkg; const connectionString = `${process.env.DATABASE_URL}`; diff --git a/src/app/middlewares/auth.ts b/src/app/middlewares/auth.ts index 5723cf3..5a39143 100644 --- a/src/app/middlewares/auth.ts +++ b/src/app/middlewares/auth.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from "express"; -import { configs } from "../configs"; -import { AppError } from "../utils/app_error"; -import { jwtHelpers, JwtPayloadType } from "../utils/JWT"; +import { configs } from "../configs/index.js"; +import { AppError } from "../utils/app_error.js"; +import { jwtHelpers, JwtPayloadType } from "../utils/JWT.js"; type Role = "ADMIN" | "USER"; diff --git a/src/app/middlewares/global_error_handler.ts b/src/app/middlewares/global_error_handler.ts index c361f7f..511506e 100644 --- a/src/app/middlewares/global_error_handler.ts +++ b/src/app/middlewares/global_error_handler.ts @@ -1,9 +1,9 @@ import { ErrorRequestHandler } from "express"; import { ZodError } from "zod"; -import { configs } from "../configs"; -import handleZodError from "../errors/zodError"; -import { TErrorSources } from "../types/error"; -import { AppError } from "../utils/app_error"; +import { configs } from "../configs/index.js"; +import handleZodError from "../errors/zodError.js"; +import { TErrorSources } from "../types/error.js"; +import { AppError } from "../utils/app_error.js"; const globalErrorHandler: ErrorRequestHandler = (err, req, res, next) => { let statusCode = 500; diff --git a/src/app/modules/account/account.controller.ts b/src/app/modules/account/account.controller.ts index f0a467d..114f159 100644 --- a/src/app/modules/account/account.controller.ts +++ b/src/app/modules/account/account.controller.ts @@ -1,7 +1,7 @@ -import { configs } from "../../configs"; -import catchAsync from "../../utils/catch_async"; -import manageResponse from "../../utils/manage_response"; -import { account_services } from "./account.service"; +import { configs } from "../../configs/index.js"; +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { account_services } from "./account.service.js"; const create_account = catchAsync(async (req, res) => { const result = await account_services.create_account_into_db(req); diff --git a/src/app/modules/account/account.route.ts b/src/app/modules/account/account.route.ts index a8300d8..3f171b2 100644 --- a/src/app/modules/account/account.route.ts +++ b/src/app/modules/account/account.route.ts @@ -1,8 +1,8 @@ import { Router } from "express"; -import auth from "../../middlewares/auth"; -import RequestValidator from "../../middlewares/request_validator"; -import { account_controller } from "./account.controller"; -import { account_validation } from "./account.validation"; +import auth from "../../middlewares/auth.js"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { account_controller } from "./account.controller.js"; +import { account_validation } from "./account.validation.js"; const accountRouter = Router(); diff --git a/src/app/modules/account/account.service.ts b/src/app/modules/account/account.service.ts index 33868fa..1ce71a7 100644 --- a/src/app/modules/account/account.service.ts +++ b/src/app/modules/account/account.service.ts @@ -1,12 +1,12 @@ import bcrypt from "bcrypt"; import { Request } from "express"; -import { configs } from "../../configs"; -import { prisma } from "../../lib/prisma"; -import { emailQueue } from "../../queues/email/email.queue"; -import { AppError } from "../../utils/app_error"; -import { jwtHelpers } from "../../utils/JWT"; -import sendMail from "../../utils/mail_sender"; -import { otpGenerator } from "../../utils/otpGenerator"; +import { configs } from "../../configs/index.js"; +import { prisma } from "../../lib/prisma.js"; +import { emailQueue } from "../../queues/email/email.queue.js"; +import { AppError } from "../../utils/app_error.js"; +import { jwtHelpers } from "../../utils/JWT.js"; +import sendMail from "../../utils/mail_sender.js"; +import { otpGenerator } from "../../utils/otpGenerator.js"; const create_account_into_db = async (req: Request) => { const payload = req?.body; @@ -22,7 +22,7 @@ const create_account_into_db = async (req: Request) => { const hashPassword = bcrypt.hashSync(payload.password, 10); // create account and profile - const result = await prisma.$transaction(async (tx) => { + const result = await prisma.$transaction(async (tx:any) => { const account = await tx.account.create({ data: { email: payload.email, diff --git a/src/app/modules/order/order.controller.ts b/src/app/modules/order/order.controller.ts index 01d2222..e0b3a6c 100644 --- a/src/app/modules/order/order.controller.ts +++ b/src/app/modules/order/order.controller.ts @@ -1,7 +1,7 @@ -import catchAsync from "../../utils/catch_async"; -import manageResponse from "../../utils/manage_response"; -import { order_service } from "./order.service"; +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { order_service } from "./order.service.js"; const get_all_order = catchAsync(async (req, res) => { const result = await order_service.get_all_order_from_db(req); diff --git a/src/app/modules/order/order.route.ts b/src/app/modules/order/order.route.ts index d002096..a6ae228 100644 --- a/src/app/modules/order/order.route.ts +++ b/src/app/modules/order/order.route.ts @@ -1,9 +1,9 @@ import { Router } from "express"; -import RequestValidator from "../../middlewares/request_validator"; -import { order_controller } from "./order.controller"; -import { order_validations } from "./order.validation"; -import auth from "../../middlewares/auth"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { order_controller } from "./order.controller.js"; +import { order_validations } from "./order.validation.js"; +import auth from "../../middlewares/auth.js"; const router = Router(); diff --git a/src/app/modules/order/order.service.ts b/src/app/modules/order/order.service.ts index 0b492fe..4a14a5b 100644 --- a/src/app/modules/order/order.service.ts +++ b/src/app/modules/order/order.service.ts @@ -1,9 +1,9 @@ import { Request } from "express"; -import { configs } from "../../configs"; -import { prisma } from "../../lib/prisma"; -import { orderEmailQueue } from "../../queues/email/order/order.email.queue"; -import { AppError } from "../../utils/app_error"; -import paginationHelper from "../../utils/pagination_helper"; +import { configs } from "../../configs/index.js"; +import { prisma } from "../../lib/prisma.js"; +import { orderEmailQueue } from "../../queues/email/order/order.email.queue.js"; +import { AppError } from "../../utils/app_error.js"; +import paginationHelper from "../../utils/pagination_helper.js"; const get_all_order_from_db = async (req: Request) => { // define your own login here diff --git a/src/app/modules/plan/plan.controller.ts b/src/app/modules/plan/plan.controller.ts index 06f8efd..170c849 100644 --- a/src/app/modules/plan/plan.controller.ts +++ b/src/app/modules/plan/plan.controller.ts @@ -1,7 +1,7 @@ -import catchAsync from "../../utils/catch_async"; -import manageResponse from "../../utils/manage_response"; -import { plan_service } from "./plan.service"; +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { plan_service } from "./plan.service.js"; const get_all_plan = catchAsync(async (req, res) => { const result = await plan_service.get_all_plan_from_db(req); diff --git a/src/app/modules/plan/plan.route.ts b/src/app/modules/plan/plan.route.ts index b05a285..2aae558 100644 --- a/src/app/modules/plan/plan.route.ts +++ b/src/app/modules/plan/plan.route.ts @@ -1,8 +1,8 @@ import { Router } from "express"; -import RequestValidator from "../../middlewares/request_validator"; -import { plan_controller } from "./plan.controller"; -import { plan_validations } from "./plan.validation"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { plan_controller } from "./plan.controller.js"; +import { plan_validations } from "./plan.validation.js"; const router = Router(); diff --git a/src/app/modules/plan/plan.service.ts b/src/app/modules/plan/plan.service.ts index ec420ed..a3ca07d 100644 --- a/src/app/modules/plan/plan.service.ts +++ b/src/app/modules/plan/plan.service.ts @@ -1,7 +1,7 @@ import { Request } from "express"; -import { prisma } from "../../lib/prisma"; -import { AppError } from "../../utils/app_error"; +import { prisma } from "../../lib/prisma.js"; +import { AppError } from "../../utils/app_error.js"; const get_all_plan_from_db = async (req: Request) => { // define your own login here diff --git a/src/app/modules/profile/profile.controller.ts b/src/app/modules/profile/profile.controller.ts index 65a0b55..5af424e 100644 --- a/src/app/modules/profile/profile.controller.ts +++ b/src/app/modules/profile/profile.controller.ts @@ -1,6 +1,6 @@ -import catchAsync from "../../utils/catch_async"; -import manageResponse from "../../utils/manage_response"; -import { profile_service } from "./profile.service"; +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { profile_service } from "./profile.service.js"; const update_profile = catchAsync(async (req, res) => { const result = await profile_service.update_profile_into_db(req); diff --git a/src/app/modules/profile/profile.route.ts b/src/app/modules/profile/profile.route.ts index 7095e3b..3a67d24 100644 --- a/src/app/modules/profile/profile.route.ts +++ b/src/app/modules/profile/profile.route.ts @@ -1,9 +1,9 @@ import { Router } from "express"; -import RequestValidator from "../../middlewares/request_validator"; -import { profile_controller } from "./profile.controller"; -import { profile_validations } from "./profile.validation"; -import auth from "../../middlewares/auth"; -import uploader from "../../middlewares/uploader"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { profile_controller } from "./profile.controller.js"; +import { profile_validations } from "./profile.validation.js"; +import auth from "../../middlewares/auth.js"; +import uploader from "../../middlewares/uploader.js"; const router = Router(); diff --git a/src/app/modules/profile/profile.service.ts b/src/app/modules/profile/profile.service.ts index 06d8a84..b93e02d 100644 --- a/src/app/modules/profile/profile.service.ts +++ b/src/app/modules/profile/profile.service.ts @@ -1,7 +1,7 @@ import { Request } from "express"; -import uploadCloud from "../../utils/cloudinary"; -import { prisma } from "../../lib/prisma"; -import { JwtPayloadType } from "../../utils/JWT"; +import uploadCloud from "../../utils/cloudinary.js"; +import { prisma } from "../../lib/prisma.js"; +import { JwtPayloadType } from "../../utils/JWT.js"; const update_profile_into_db = async (req: Request) => { const user = req?.user as JwtPayloadType; diff --git a/src/app/modules/support/support.controller.ts b/src/app/modules/support/support.controller.ts index 25faf3f..7c4e8e5 100644 --- a/src/app/modules/support/support.controller.ts +++ b/src/app/modules/support/support.controller.ts @@ -1,8 +1,8 @@ import { Request, Response } from "express"; -import catchAsync from "../../utils/catch_async"; -import manageResponse from "../../utils/manage_response"; -import { support_service } from "./support.service"; +import catchAsync from "../../utils/catch_async.js"; +import manageResponse from "../../utils/manage_response.js"; +import { support_service } from "./support.service.js"; diff --git a/src/app/modules/support/support.route.ts b/src/app/modules/support/support.route.ts index bf447e1..db5c0ea 100644 --- a/src/app/modules/support/support.route.ts +++ b/src/app/modules/support/support.route.ts @@ -1,9 +1,9 @@ import { Router } from "express"; -import RequestValidator from "../../middlewares/request_validator"; -import { support_controller } from "./support.controller"; -import { support_validations } from "./support.validation"; -import auth from "../../middlewares/auth"; +import RequestValidator from "../../middlewares/request_validator.js"; +import { support_controller } from "./support.controller.js"; +import { support_validations } from "./support.validation.js"; +import auth from "../../middlewares/auth.js"; const router = Router(); diff --git a/src/app/modules/support/support.service.ts b/src/app/modules/support/support.service.ts index c970fbe..43e2e0b 100644 --- a/src/app/modules/support/support.service.ts +++ b/src/app/modules/support/support.service.ts @@ -1,6 +1,6 @@ -import { prisma } from "../../lib/prisma"; -import { Prisma } from "../../../../prisma/generated/prisma/client"; -import { AppError } from "../../utils/app_error"; +import { prisma } from "../../lib/prisma.js"; +import { Prisma } from "@prisma/client"; +import { AppError } from "../../utils/app_error.js"; const createSupportIntoDB = async (payload: any) => { const result = await prisma.support.create({ data: payload }); diff --git a/src/app/queues/connection.ts b/src/app/queues/connection.ts index dd536c2..22cab1d 100644 --- a/src/app/queues/connection.ts +++ b/src/app/queues/connection.ts @@ -1,6 +1,7 @@ -import { QueueOptions } from "bullmq"; +import { Redis } from "ioredis"; +import { configs } from "../configs/index.js"; -export const redisConnection: QueueOptions["connection"] = { - host: "127.0.0.1", - port: 6379, -}; \ No newline at end of file +export const redisConnection = new Redis(configs.redis_url as string, { + tls: {}, + maxRetriesPerRequest: null, +}); \ No newline at end of file diff --git a/src/app/queues/email/email.processor.ts b/src/app/queues/email/email.processor.ts index 496f4db..0097270 100644 --- a/src/app/queues/email/email.processor.ts +++ b/src/app/queues/email/email.processor.ts @@ -1,6 +1,6 @@ -import { otpTemplate } from "../../templates/otpTemplate"; -import sendMail from "../../utils/mail_sender"; -import { TEmailQueue } from "./email.queue"; +import { otpTemplate } from "../../templates/otpTemplate.js"; +import sendMail from "../../utils/mail_sender.js"; +import { TEmailQueue } from "./email.queue.js"; // email.processor.ts export const emailProcessor = async (job: any) => { diff --git a/src/app/queues/email/email.queue.ts b/src/app/queues/email/email.queue.ts index 31c25f4..7fe1cc3 100644 --- a/src/app/queues/email/email.queue.ts +++ b/src/app/queues/email/email.queue.ts @@ -1,6 +1,6 @@ // email.queue.ts import { Queue } from "bullmq"; -import { redisConnection } from "../connection"; +import { redisConnection } from "../connection.js"; export type TEmailQueue = { email: string; diff --git a/src/app/queues/email/email.worker.ts b/src/app/queues/email/email.worker.ts index 89a66e2..62e4e1d 100644 --- a/src/app/queues/email/email.worker.ts +++ b/src/app/queues/email/email.worker.ts @@ -1,8 +1,8 @@ // email.worker.ts import { Worker } from "bullmq"; -import { redisConnection } from "../connection"; -import { emailProcessor } from "./email.processor"; -import { TEmailQueue } from "./email.queue"; +import { redisConnection } from "../connection.js"; +import { emailProcessor } from "./email.processor.js"; +import { TEmailQueue } from "./email.queue.js"; export const emailWorker = new Worker( "email-queue", diff --git a/src/app/queues/email/order/order.email.processor.ts b/src/app/queues/email/order/order.email.processor.ts index 1a81c35..4d5b74b 100644 --- a/src/app/queues/email/order/order.email.processor.ts +++ b/src/app/queues/email/order/order.email.processor.ts @@ -1,5 +1,5 @@ -import sendMail from "../../../utils/mail_sender"; -import { TOrderEmailQueue } from "./order.email.queue"; +import sendMail from "../../../utils/mail_sender.js"; +import { TOrderEmailQueue } from "./order.email.queue.js"; // email.processor.ts export const orderEmailProcessor = async (job: any) => { diff --git a/src/app/queues/email/order/order.email.queue.ts b/src/app/queues/email/order/order.email.queue.ts index db7d087..ea2cfcb 100644 --- a/src/app/queues/email/order/order.email.queue.ts +++ b/src/app/queues/email/order/order.email.queue.ts @@ -1,5 +1,5 @@ import { Queue } from "bullmq"; -import { redisConnection } from "../../connection"; +import { redisConnection } from "../../connection.js"; export type TOrderEmailQueue = { email: string; diff --git a/src/app/queues/email/order/order.email.worker.ts b/src/app/queues/email/order/order.email.worker.ts index eb3e400..663840b 100644 --- a/src/app/queues/email/order/order.email.worker.ts +++ b/src/app/queues/email/order/order.email.worker.ts @@ -1,8 +1,8 @@ // email.worker.ts import { Worker } from "bullmq"; -import { redisConnection } from "../../connection"; -import { orderEmailProcessor } from "./order.email.processor"; -import { TOrderEmailQueue } from "./order.email.queue"; +import { redisConnection } from "../../connection.js"; +import { orderEmailProcessor } from "./order.email.processor.js"; +import { TOrderEmailQueue } from "./order.email.queue.js"; export const emailWorker = new Worker( diff --git a/src/app/queues/worker.ts b/src/app/queues/worker.ts index 71a333d..7aeafe5 100644 --- a/src/app/queues/worker.ts +++ b/src/app/queues/worker.ts @@ -1,4 +1,4 @@ -import "./email/email.worker"; -import "./email/order/order.email.worker"; +import "./email/email.worker.js"; +import "./email/order/order.email.worker.js"; console.log("Workers running..."); \ No newline at end of file diff --git a/src/app/templates/otpTemplate.ts b/src/app/templates/otpTemplate.ts index ef9d04c..92622ba 100644 --- a/src/app/templates/otpTemplate.ts +++ b/src/app/templates/otpTemplate.ts @@ -1,4 +1,4 @@ -import { TEmailQueue } from "../queues/email/email.queue" +import { TEmailQueue } from "../queues/email/email.queue.js" export const otpTemplate = (payload: TEmailQueue) => { return ` diff --git a/src/app/utils/cloudinary.ts b/src/app/utils/cloudinary.ts index ae77b6b..0ef6d23 100644 --- a/src/app/utils/cloudinary.ts +++ b/src/app/utils/cloudinary.ts @@ -1,6 +1,6 @@ import { v2 as cloudinary } from 'cloudinary'; import fs from 'fs'; -import { configs } from '../configs'; +import { configs } from '../configs/index.js'; type ICloudinaryResponse = { asset_id: string; diff --git a/src/app/utils/mail_sender.ts b/src/app/utils/mail_sender.ts index 9a45052..7257106 100644 --- a/src/app/utils/mail_sender.ts +++ b/src/app/utils/mail_sender.ts @@ -1,5 +1,5 @@ import nodemailer from 'nodemailer'; -import { configs } from '../configs'; +import { configs } from '../configs/index.js'; type TMailContent = { to: string, subject: string, diff --git a/src/routes.ts b/src/routes.ts index 7dcbaa3..a7ac1a2 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,9 +1,9 @@ import { Router } from "express"; -import accountRouter from "./app/modules/account/account.route"; -import orderRoute from "./app/modules/order/order.route"; -import planRoute from "./app/modules/plan/plan.route"; -import profileRoute from "./app/modules/profile/profile.route"; -import supportRoute from "./app/modules/support/support.route"; +import accountRouter from "./app/modules/account/account.route.js"; +import orderRoute from "./app/modules/order/order.route.js"; +import planRoute from "./app/modules/plan/plan.route.js"; +import profileRoute from "./app/modules/profile/profile.route.js"; +import supportRoute from "./app/modules/support/support.route.js"; const appRouter = Router(); diff --git a/src/server.ts b/src/server.ts index b265e0d..bf6d3ab 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ -import app from "./app"; -import { configs } from "./app/configs/index"; -import { prisma } from "./app/lib/prisma"; -import "./app/queues/worker"; +import app from "./app.js"; +import { configs } from "./app/configs/index.js"; +import { prisma } from "./app/lib/prisma.js"; +import "./app/queues/worker.js"; async function main() { try { diff --git a/src/swaggerOptions.ts b/src/swaggerOptions.ts index 2b898f7..93859d5 100644 --- a/src/swaggerOptions.ts +++ b/src/swaggerOptions.ts @@ -1,11 +1,11 @@ import { fileURLToPath } from "node:url"; import path from "path"; -import { configs } from "./app/configs"; -import { accountSwaggerDocs } from "./app/modules/account/account.swagger"; -import { orderSwaggerDocs } from "./app/modules/order/order.swagger"; -import { planSwaggerDocs } from "./app/modules/plan/plan.swagger"; -import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger"; -import { supportSwaggerDocs } from "./app/modules/support/support.swagger"; +import { configs } from "./app/configs/index.js"; +import { accountSwaggerDocs } from "./app/modules/account/account.swagger.js"; +import { orderSwaggerDocs } from "./app/modules/order/order.swagger.js"; +import { planSwaggerDocs } from "./app/modules/plan/plan.swagger.js"; +import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger.js"; +import { supportSwaggerDocs } from "./app/modules/support/support.swagger.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -27,8 +27,8 @@ export const swaggerOptions = { }, servers: configs.env === "production" - ? [{ url: "https://live-url.com" }, { url: "http://localhost:5000" }] - : [{ url: "http://localhost:5000" }, { url: "https://live-url.com" }], + ? [{ url: "https://quicklunch-server.onrender.com" }, { url: "http://localhost:5000" }] + : [{ url: "http://localhost:5000" }, { url: "https://quicklunch-server.onrender.com" }], components: { securitySchemes: { AuthorizationToken: { diff --git a/tsconfig.json b/tsconfig.json index f40b4eb..0c4d9a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "target": "ES2023", - "module": "ESNext", - "moduleResolution": "bundler", - "rootDir": "./", + "module": "nodenext", + "moduleResolution": "nodenext", + "rootDir": "./src", "outDir": "./dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, @@ -11,8 +11,7 @@ "skipLibCheck": true }, "include": [ - "src/**/*", - "prisma/**/*" + "src/**/*" ], "exclude": [ "node_modules",