6 Commits

Author SHA1 Message Date
abumahid 7bf4bc93f1 Merge pull request 'Sharafat' (#11) from sharafat into main
Reviewed-on: #11
2026-06-17 16:14:29 +00:00
sharafat f224ff6bf0 Add api: Users 2026-06-17 22:01:03 +06:00
abumahid 4468c00abf Merge pull request 'Remove dist folder from repository' (#1) from enh/remove-dist into main
Reviewed-on: #1
2026-06-17 14:18:19 +00:00
abumahid 0a8fe573ae Remove dist folder from repository 2026-06-17 20:15:51 +06:00
sharafat c05266a522 USER API's resolve the issues 2026-05-24 23:37:13 +06:00
sharafat 1abecc9b8f Update:PROFILE API's and create get users api's with pagination 2026-05-24 00:17:16 +06:00
83 changed files with 404 additions and 3284 deletions
Vendored
-37
View File
@@ -1,37 +0,0 @@
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:5173"],
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;
-26
View File
@@ -1,26 +0,0 @@
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,
},
redis_url: process.env.REDIS_URL,
};
-15
View File
@@ -1,15 +0,0 @@
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;
-8
View File
@@ -1,8 +0,0 @@
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 };
-23
View File
@@ -1,23 +0,0 @@
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;
-47
View File
@@ -1,47 +0,0 @@
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;
-8
View File
@@ -1,8 +0,0 @@
const notFound = (req, res, next) => {
res.status(404).json({
message: 'Sorry Route is not found!! 😴😴😴',
success: false,
error: '',
});
};
export default notFound;
-12
View File
@@ -1,12 +0,0 @@
const RequestValidator = (schema) => {
return async (req, res, next) => {
try {
req.body = await schema.parseAsync(req.body);
next();
}
catch (err) {
next(err);
}
};
};
export default RequestValidator;
-12
View File
@@ -1,12 +0,0 @@
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;
-101
View File
@@ -1,101 +0,0 @@
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.accessToken, {
secure: configs.env === "production",
httpOnly: true,
});
manageResponse(res, {
statusCode: 200,
success: true,
message: "User logged in successfully",
data: 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
};
-16
View File
@@ -1,16 +0,0 @@
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;
-371
View File
@@ -1,371 +0,0 @@
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"
}, {
attempts: 1,
removeOnComplete: true,
removeOnFail: true,
});
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,
},
select: {
id: true,
email: true,
role: true,
isAccountVerified: true,
isDeleted: true,
password: true,
profile: true,
},
});
// 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);
const finalOutputData = {
id: account.id,
email: account.email,
role: account.role,
shopName: account?.profile?.shopName,
shopLogo: account?.profile?.shopLogo,
};
return {
accessToken,
profile: finalOutputData
};
};
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: `
<p><strong>OTP</strong> ${newOtp}</p>
<small>Otp will be expire in 5 minutes</small>
<br/> <br/>
<p>Or you can use Verification link </p>
<p>${verificationLink}</p>
`,
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: `
<p>Your Reset Link: </p>
<p>${verificationLink}</p>
`,
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
};
-187
View File
@@ -1,187 +0,0 @@
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" },
},
},
},
};
-37
View File
@@ -1,37 +0,0 @@
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
};
-60
View File
@@ -1,60 +0,0 @@
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,
};
-12
View File
@@ -1,12 +0,0 @@
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;
-163
View File
@@ -1,163 +0,0 @@
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: `<p>Your order has been created. Track your order here: <a href="${trackingLink}">Track Order</a></p>`,
});
}
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 user = req.user;
if (user?.role !== "ADMIN") {
throw new AppError("You are not authorized to perform this action", 403);
}
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,
};
-165
View File
@@ -1,165 +0,0 @@
export const orderSwaggerDocs = {
"/api/order": {
post: {
tags: ["order"],
summary: "Create new order",
description: ` INITIATED
CONFIRMED
ONGOING
DELIVERED
CANCELLED`,
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 -(Admin route)",
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" },
},
},
},
};
-19
View File
@@ -1,19 +0,0 @@
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,
};
-60
View File
@@ -1,60 +0,0 @@
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,
};
-12
View File
@@ -1,12 +0,0 @@
import { Router } from "express";
import auth from "../../middlewares/auth.js";
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), auth("ADMIN"), plan_controller.create_plan);
router.get("/:id", plan_controller.get_single_plan);
router.patch("/:id", RequestValidator(plan_validations.update_plan), auth("ADMIN"), plan_controller.update_plan);
router.delete("/:id", auth("ADMIN"), plan_controller.delete_plan);
export default router;
-68
View File
@@ -1,68 +0,0 @@
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 dont 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 dont 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 dont 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,
};
-125
View File
@@ -1,125 +0,0 @@
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" },
},
},
},
};
-33
View File
@@ -1,33 +0,0 @@
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,
};
-15
View File
@@ -1,15 +0,0 @@
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,
};
-12
View File
@@ -1,12 +0,0 @@
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;
-23
View File
@@ -1,23 +0,0 @@
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;
console.log(payload);
// 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,
};
-34
View File
@@ -1,34 +0,0 @@
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" },
},
},
},
};
-7
View File
@@ -1,7 +0,0 @@
import { z } from "zod";
const update_profile = z.object({
fullName: z.string().optional(),
});
export const profile_validations = {
update_profile,
};
-80
View File
@@ -1,80 +0,0 @@
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,
};
-12
View File
@@ -1,12 +0,0 @@
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;
-100
View File
@@ -1,100 +0,0 @@
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,
};
-109
View File
@@ -1,109 +0,0 @@
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" },
},
},
},
};
-28
View File
@@ -1,28 +0,0 @@
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,
};
-7
View File
@@ -1,7 +0,0 @@
import { Redis } from "ioredis";
import { configs } from "../configs/index.js";
export const redisConnection = new Redis(configs.redis_url, {
tls: {},
maxRetriesPerRequest: null,
enableReadyCheck: false,
});
-14
View File
@@ -1,14 +0,0 @@
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);
};
-6
View File
@@ -1,6 +0,0 @@
// email.queue.ts
import { Queue } from "bullmq";
import { redisConnection } from "../connection.js";
export const emailQueue = new Queue("email-queue", {
connection: redisConnection,
});
-7
View File
@@ -1,7 +0,0 @@
// 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,
});
-12
View File
@@ -1,12 +0,0 @@
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);
};
-5
View File
@@ -1,5 +0,0 @@
import { Queue } from "bullmq";
import { redisConnection } from "../../connection.js";
export const orderEmailQueue = new Queue("order-email-queue", {
connection: redisConnection,
});
-7
View File
@@ -1,7 +0,0 @@
// 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,
});
-3
View File
@@ -1,3 +0,0 @@
import "./email/email.worker.js";
import "./email/order/order.email.worker.js";
console.log("Workers running...");
-80
View File
@@ -1,80 +0,0 @@
export const otpTemplate = (payload) => {
return `
<div
style="
margin: 0;
padding: 0;
background-color: #f4f6f8;
font-family: Arial, Helvetica, sans-serif;
"
>
<table
align="center"
width="100%"
cellpadding="0"
cellspacing="0"
style="
max-width: 600px;
margin: auto;
background: #ffffff;
border-radius: 8px;
overflow: hidden;
"
>
<tr>
<td style="padding: 30px; color: #333333">
<p style="margin: 0 0 20px 0; font-size: 15px">
Use the following One-Time Password (OTP) to complete your
verification:
</p>
<!-- OTP Box -->
<div style="text-align: center; margin: 25px 0">
<span
style="
display: inline-block;
background: #f1f5f9;
padding: 15px 25px;
font-size: 24px;
letter-spacing: 4px;
font-weight: bold;
color: #111827;
border-radius: 6px;
"
>
${payload.otp}
</span>
</div>
<p style="margin: 0 0 20px 0; font-size: 13px; color: #6b7280">
This OTP will expire in <strong>5 minutes</strong>.
</p>
<!-- Divider -->
<hr
style="
border: none;
border-top: 1px solid #e5e7eb;
margin: 25px 0;
"
/>
<!-- Verification Link -->
<p style="margin: 0 0 10px 0; font-size: 15px">
Or verify using this link:
</p>
<p style="word-break: break-all; font-size: 14px">
<a
href="${payload.verificationLink}"
style="color: #4f46e5; text-decoration: none"
>
${payload.verificationLink}
</a>
</p>
</td>
</tr>
</table>
</div>
`;
};
-15
View File
@@ -1,15 +0,0 @@
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,
};
-13
View File
@@ -1,13 +0,0 @@
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);
}
}
}
-11
View File
@@ -1,11 +0,0 @@
const catchAsync = (fn) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
}
catch (error) {
next(error);
}
};
};
export default catchAsync;
-23
View File
@@ -1,23 +0,0 @@
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;
-113
View File
@@ -1,113 +0,0 @@
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: `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Welcome Email</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Fallback styles for unsupported clients (some email clients ignore <style> tags) */
@media only screen and (max-width: 600px) {
.container {
padding: 20px !important;
}
.btn {
padding: 12px 18px !important;
font-size: 16px !important;
}
}
</style>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif">
<div
style="
max-width: 600px;
margin: 40px auto;
background-color: #f4f4f4;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
"
class="container"
>
<div style="font-size: 16px; color: #555555; line-height: 1.6">
<p style="margin-bottom: 30px">
Hi <strong>${payload?.name || ""}</strong>,
</p>
${payload?.htmlBody}
<div style="margin-top: 60px; text-align: center">
<img
style="width: 50px; height: 50px; border-radius: 50%"
src="https://i.ibb.co.com/RkFJjPWg/quick-launch-1.png"
alt="Quick Launch"
/>
<p style="font-size: 12px">The Support Team</p>
<h3>Quick Launch</h3>
</div>
</div>
<p
style="
font-size: 14px;
color: #999999;
margin-top: 20px;
margin-bottom: 10px;
text-align: center;
"
>
This is an automated message — please do not reply to this email.
<br />
If you need assistance, feel free to contact our support team.
<br /><br />
Thank you for choosing us!
</p>
<hr />
<div
style="
text-align: center;
font-size: 12px;
color: #999999;
margin-top: 20px;
"
>
&copy; 2026 to {{year}} Quick Launch. All rights reserved.
</div>
</div>
</body>
</html>
`,
});
return info;
};
export default sendMail;
-9
View File
@@ -1,9 +0,0 @@
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;
-23
View File
@@ -1,23 +0,0 @@
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;
};
-15
View File
@@ -1,15 +0,0 @@
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;
-16
View File
@@ -1,16 +0,0 @@
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;
-19
View File
@@ -1,19 +0,0 @@
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);
});
-48
View File
@@ -1,48 +0,0 @@
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://quicklunch-server.onrender.com" }, { url: "http://localhost:5000" }]
: [{ url: "http://localhost:5000" }, { url: "https://quicklunch-server.onrender.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"),
],
};
-1
View File
@@ -20,7 +20,6 @@
"cloudinary": "^2.7.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dayjs": "^1.11.20",
"dotenv": "^17.3.1",
"express": "^5.1.0",
"ioredis": "^5.10.1",
@@ -0,0 +1,14 @@
-- CreateEnum
CREATE TYPE "ShopStatus" AS ENUM ('ACTIVE', 'SUSPENDED', 'DELETED');
-- AlterTable
ALTER TABLE "Profile" ADD COLUMN "status" "ShopStatus" NOT NULL DEFAULT 'ACTIVE';
-- CreateTable
CREATE TABLE "Users" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Users_pkey" PRIMARY KEY ("id")
);
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "ShopStatus" ADD VALUE 'INACTIVE';
+11 -5
View File
@@ -1,13 +1,19 @@
enum ShopStatus {
ACTIVE
SUSPENDED
DELETED
INACTIVE
}
model Profile {
id String @id @default(uuid())
accountId String @unique
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
id String @id @default(uuid())
accountId String @unique
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
shopName String
shopLogo String?
contactNumber String?
shopAddress String?
shopMapLocation String?
shopCategory String?
status ShopStatus @default(ACTIVE)
}
+7
View File
@@ -0,0 +1,7 @@
model Users {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -1,34 +0,0 @@
import { Request, Response } from "express";
import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response.js";
import { analytics_service } from "./analytics.service.js";
const getOverview = catchAsync(async (req: Request, res: Response) => {
const range = (req.query.range as "7d" | "30d") || "7d";
const result = await analytics_service.getOverview(range);
manageResponse(res, {
success: true,
statusCode: 200,
message: "Analytics overview fetched successfully.",
data: result,
meta: {},
});
});
const getLastSevenDaysRevenue = catchAsync(
async (req: Request, res: Response) => {
const result = await analytics_service.getLastSevenDaysRevenue();
manageResponse(res, {
success: true,
statusCode: 200,
message: "Analytics overview fetched successfully.",
data: result,
meta: {},
});
},
);
export const analytics_controller = {
getOverview,
getLastSevenDaysRevenue,
};
@@ -1,25 +0,0 @@
import { Router } from "express";
// import RequestValidator from "../../middlewares/request_validator.js";
import { analytics_controller } from "./analytics.controller.js";
import auth from "../../middlewares/auth.js";
// import { analytics_validations } from "./analytics.validation.js";
//! "/admin/analytics"
const router = Router();
router.get("/overview", auth("ADMIN"), analytics_controller.getOverview);
router.get("/revenue-overview", auth("ADMIN"), analytics_controller.getLastSevenDaysRevenue);
// router.post(
// "/",
// RequestValidator(analytics_validations.create_analytics),
// analytics_controller.create_analytics,
// );
export default router;
@@ -1,190 +0,0 @@
import { Request } from "express";
import { prisma } from "../../lib/prisma.js";
import dayjs from "dayjs";
const getOverview = async (range: "7d" | "30d") => {
const days = range === "30d" ? 30 : 7;
const now = dayjs();
const currentStartDate = now.subtract(days, "day").startOf("day").toDate();
const currentEndDate = now.endOf("day").toDate();
const previousStartDate = now
.subtract(days * 2, "day")
.startOf("day")
.toDate();
const previousEndDate = now.subtract(days, "day").endOf("day").toDate();
const rangeFilter = (start: Date, end: Date) => ({
gte: start,
lte: end,
});
const [
currentActiveStores,
previousActiveStores,
currentRevenue,
previousRevenue,
currentSignups,
previousSignups,
currentPendingActions,
previousPendingActions,
] = await Promise.all([
prisma.profile.count({
where: {
account: {
isAccountVerified: true,
},
},
}),
prisma.profile.count({
where: {
account: {
isDeleted: false,
isAccountVerified: true,
createdAt: rangeFilter(previousStartDate, previousEndDate),
},
},
}),
prisma.order.aggregate({
_sum: {
productPrice: true,
},
where: {
status: "DELIVERED",
createdAt: rangeFilter(currentStartDate, currentEndDate),
},
}),
prisma.order.aggregate({
_sum: {
productPrice: true,
},
where: {
status: "DELIVERED",
createdAt: rangeFilter(previousStartDate, previousEndDate),
},
}),
prisma.account.count({
where: {
createdAt: rangeFilter(currentStartDate, currentEndDate),
},
}),
prisma.account.count({
where: {
createdAt: rangeFilter(previousStartDate, previousEndDate),
},
}),
prisma.order.count({
where: {
status: {
in: ["INITIATED", "CONFIRMED"],
},
createdAt: rangeFilter(currentStartDate, currentEndDate),
},
}),
prisma.order.count({
where: {
status: {
in: ["INITIATED", "CONFIRMED"],
},
createdAt: rangeFilter(previousStartDate, previousEndDate),
},
}),
]);
const percentage = (current: number, previous: number) => {
// avoid division by zero
if (previous === 0) return current > 0 ? 100 : 0;
return Number((((current - previous) / previous) * 100).toFixed(2));
};
const currentRevenueTotal = currentRevenue._sum.productPrice ?? 0;
const previousRevenueTotal = previousRevenue._sum.productPrice ?? 0;
return {
activeStores: {
total: currentActiveStores,
changePercentage: percentage(currentActiveStores, previousActiveStores),
},
revenue: {
total: currentRevenueTotal,
changePercentage: percentage(currentRevenueTotal, previousRevenueTotal),
},
signups: {
total: currentSignups,
changePercentage: percentage(currentSignups, previousSignups),
},
pendingActions: {
total: currentPendingActions,
changePercentage: percentage(
currentPendingActions,
previousPendingActions,
),
},
};
};
const getLastSevenDaysRevenue = async () => {
const startDate = dayjs().subtract(6, "day").startOf("day").toDate();
const endDate = dayjs().endOf("day").toDate();
const orders = await prisma.order.findMany({
where: {
status: "DELIVERED",
createdAt: {
gte: startDate,
lte: endDate,
},
},
select: {
productPrice: true,
productQuantity: true,
createdAt: true,
},
});
const revenueMap: Record<string, number> = {};
for (let i = 0; i < 7; i++) {
const date = dayjs(startDate).add(i, "day");
revenueMap[date.format("dddd")] = 0;
}
for (const order of orders) {
const day = dayjs(order.createdAt).format("dddd");
revenueMap[day] += order.productPrice * order.productQuantity;
}
const data = Object.entries(revenueMap).map(([day, revenue]) => ({
day,
revenue,
}));
return data;
};
export const analytics_service = {
getOverview, getLastSevenDaysRevenue
};
@@ -1,92 +0,0 @@
export const analyticsSwaggerDocs = {
"/api/admin/analytics/overview": {
get: {
tags: ["Analytics"],
summary: "Get analytics overview (7d / 30d comparison)",
description:
"Returns aggregated analytics including active stores, revenue, signups, and percentage change compared to previous period.",
parameters: [
{
name: "range",
in: "query",
required: true,
description: "Time range for analytics overview",
schema: {
type: "string",
enum: ["7d", "30d"],
example: "7d",
},
},
],
responses: {
200: {
description: "Analytics overview fetched successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
activeStores: {
type: "object",
properties: {
total: { type: "number", example: 120 },
changePercentage: {
type: "number",
example: 5.23,
},
},
},
revenue: {
type: "object",
properties: {
total: { type: "number", example: 45230 },
changePercentage: {
type: "number",
example: -3.45,
},
},
},
signups: {
type: "object",
properties: {
total: { type: "number", example: 340 },
changePercentage: {
type: "number",
example: 12.1,
},
},
},
},
},
},
},
},
401: {
description: "Unauthorized",
},
500: {
description: "Internal server error",
},
},
},
},
"/api/admin/analytics/revenue-overview": {
get: {
tags: ["Analytics"],
summary: "Get analytics revenue overview (last 7 days)",
description: "",
responses: {
200: {
description: "Revenue overview fetched successfully",
},
401: {
description: "Unauthorized",
},
500: {
description: "Internal server error",
},
},
},
},
};
@@ -1,10 +0,0 @@
import { z } from "zod";
const create_analytics = z.object({});
const update_analytics = z.object({});
export const analytics_validations = {
create_analytics,
update_analytics,
};
+1 -2
View File
@@ -130,7 +130,7 @@ const get_single_order_from_db = async (req: Request) => {
const create_order_into_db = async (req: Request) => {
const payload = req?.body;
console.log(payload);
payload.status = "INITIATED";
payload.paymentType = "COD";
@@ -153,7 +153,6 @@ const create_order_into_db = async (req: Request) => {
const update_order_into_db = async (req: Request) => {
// 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);
}
@@ -7,7 +7,6 @@ const update_profile_into_db = async (req: Request) => {
const user = req?.user as JwtPayloadType;
const payload = req?.body;
const file = req?.file;
console.log(payload);
// check file and upload to cloud
if (file) {
const cloudRes = await uploadCloud(file);
+1 -1
View File
@@ -13,7 +13,7 @@ export const profileSwaggerDocs = {
data: {
type: "object",
properties: {
fullName: { type: "string" },
shopName: { type: "string" },
},
},
file: {
+10 -1
View File
@@ -1,6 +1,15 @@
import { z } from "zod";
const update_profile = z.object({
fullName: z.string().optional(),
shopName: z.string().optional(),
shopAddress: z.string().optional(),
shopPhone: z.string().optional(),
shopLocation: z.string().optional(),
shopImage: z.string().optional(),
shopMapLocation: z.string().optional(),
contactNumber: z.string().optional(),
shopCategory: z.string().optional(),
});
export const profile_validations = {
@@ -1,92 +0,0 @@
import manageResponse from "../../utils/manage_response";
import { Request, Response } from "express";
import catchAsync from "../../utils/catch_async";
import { statictics_service } from "./statictics.service";
const get_seller_stats = catchAsync(async (req: Request, res: Response) => {
const shopAccountId = req.user?.accountId;
if (!shopAccountId) {
return res.status(401).json({
success: false,
message: "Unauthorized",
});
}
const range = (req.query.range as "7d" | "30d" | "all") || "7d";
const result = await statictics_service.get_seller_stats_fromDb(
shopAccountId,
range,
);
manageResponse(res, {
success: true,
statusCode: 200,
message: "All statictics fetched successfully.",
data: result,
meta: {},
});
});
// const get_all_statictics = catchAsync(async (req, res) => {
// const result = await statictics_service.get_all_statictics_from_db(req);
// manageResponse(res, {
// success: true,
// statusCode: 200,
// message: "All statictics fetched successfully.",
// data: result,
// meta: {},
// });
// });
// const get_single_statictics = catchAsync(async (req, res) => {
// const result = await statictics_service.get_single_statictics_from_db(req);
// manageResponse(res, {
// success: true,
// statusCode: 200,
// message: "Single statictics fetched successfully.",
// data: result,
// meta: {},
// });
// });
// const create_statictics = catchAsync(async (req, res) => {
// const result = await statictics_service.create_statictics_into_db(req);
// manageResponse(res, {
// success: true,
// statusCode: 200,
// message: "statictics created successfully.",
// data: result,
// meta: {},
// });
// });
// const update_statictics = catchAsync(async (req, res) => {
// const result = await statictics_service.update_statictics_into_db(req);
// manageResponse(res, {
// success: true,
// statusCode: 200,
// message: "statictics updated successfully.",
// data: result,
// meta: {},
// });
// });
// const delete_statictics = catchAsync(async (req, res) => {
// const result = await statictics_service.delete_statictics_from_db(req);
// manageResponse(res, {
// success: true,
// statusCode: 200,
// message: "statictics deleted successfully.",
// data: result,
// meta: {},
// });
// });
export const statictics_controller = {
get_seller_stats,
// get_all_statictics,
// get_single_statictics,
// create_statictics,
// update_statictics,
// delete_statictics,
};
@@ -1,24 +0,0 @@
import { Router } from "express";
import auth from "../../middlewares/auth.js";
import { statictics_controller } from "./statictics.controller.js";
// import { statictics_controller } from "./statictics.controller";
// import { statictics_validations } from "./statictics.validation";
const router = Router();
router.get("/seller", auth("USER"), statictics_controller.get_seller_stats);
// router.post(
// "/",
// RequestValidator(statictics_validations.create_statictics),
// statictics_controller.create_statictics,
// );
// router.get("/:id", statictics_controller.get_single_statictics);
// router.patch(
// "/:id",
// RequestValidator(statictics_validations.update_statictics),
// statictics_controller.update_statictics,
// );
// router.delete("/:id", statictics_controller.delete_statictics);
export const staticticsRoute = router;
@@ -1,151 +0,0 @@
// import { Request } from "express";
// import { prisma } from "../../lib/prisma";
import { prisma } from "../../lib/prisma";
type Range = "7d" | "30d" | "all";
const get_seller_stats_fromDb = async (shopAccountId: string, range: Range) => {
let createdAtFilter: any = {};
if (range !== "all") {
const days = range === "7d" ? 7 : 30;
const from = new Date();
from.setDate(from.getDate() - days);
createdAtFilter = { gte: from };
}
const baseWhere: any = {
shopAccountId,
...(range !== "all" && { createdAt: createdAtFilter }),
};
const [
totalOrders,
completedOrders,
pendingOrders,
rejectedOrders,
revenueResult,
last7DaysRejected,
ordersForChart,
] = await Promise.all([
prisma.order.count({ where: baseWhere }),
prisma.order.count({
where: { ...baseWhere, status: "DELIVERED" },
}),
prisma.order.count({
where: {
...baseWhere,
status: { in: ["INITIATED", "CONFIRMED", "ONGOING"] },
},
}),
prisma.order.count({
where: { ...baseWhere, status: "CANCELLED" },
}),
prisma.order.aggregate({
where: { ...baseWhere, status: "DELIVERED" },
_sum: { productPrice: true },
}),
prisma.order.count({
where: {
shopAccountId,
status: "CANCELLED",
createdAt: {
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
},
},
}),
range !== "all"
? prisma.order.findMany({
where: {
shopAccountId,
status: "DELIVERED",
createdAt: createdAtFilter,
},
select: {
productPrice: true,
createdAt: true,
},
})
: Promise.resolve([]),
]);
const totalRevenue = revenueResult._sum.productPrice || 0;
const avgOrderValue =
completedOrders > 0 ? totalRevenue / completedOrders : 0;
const dailyMap: Record<string, number> = {};
for (const o of ordersForChart) {
const date = o.createdAt.toISOString().split("T")[0];
if (!dailyMap[date]) dailyMap[date] = 0;
dailyMap[date] += o.productPrice;
}
const dailyRevenue = Object.entries(dailyMap).map(([date, revenue]) => ({
date,
revenue,
}));
return {
totalOrders,
completedOrders,
pendingOrders,
rejectedOrders,
totalRevenue,
avgOrderValue,
last7DaysRejected,
dailyRevenue,
};
};
// const get_all_statictics_from_db = async (req: Request) => {
// // define your own login here
// const result = await prisma.statictics.findMany();
// return result;
// };
// const get_single_statictics_from_db = async (req: Request) => {
// // define your own login here
// const { id } = req.params;
// const result = await prisma.statictics.findUnique({where:{id}});
// return result;
// };
// const create_statictics_into_db = async (req: Request) => {
// // define your own login here
// const result = await prisma.statictics.create({data:req.body});
// return result;
// };
// const update_statictics_into_db = async (req: Request) => {
// // define your own login here
// const { id } = req.params;
// const result = await prisma.statictics.update({where:{id},data:req.body});
// return result;
// };
// const delete_statictics_from_db = async (req: Request) => {
// // define your own login here
// const { id } = req.params;
// const result = await prisma.statictics.delete({where:{id}});
// return result;
// };
export const statictics_service = {
get_seller_stats_fromDb,
// get_all_statictics_from_db,
// get_single_statictics_from_db,
// create_statictics_into_db,
// update_statictics_into_db,
// delete_statictics_from_db,
};
@@ -1,11 +1,12 @@
export const staticticsSwaggerDocs = {
export const staticticsSwaggerDocs = {
"/api/statictics": {
post: {
tags: ["statictics"],
summary: "Create new statictics",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
@@ -44,34 +45,33 @@
},
"/api/statictics/seller": {
get: {
tags: ["statistics"],
summary: "Get seller statistics",
description: "Fetch seller dashboard stats (7d, 30d, all)",
tags: ["statistics"],
summary: "Get seller statistics",
description: "Fetch seller dashboard stats (7d, 30d, all)",
parameters: [
{
name: "range",
in: "query",
required: false,
schema: {
type: "string",
enum: ["7d", "30d", "all"],
default: "7d",
parameters: [
{
name: "range",
in: "query",
required: false,
schema: {
type: "string",
enum: ["7d", "30d", "all"],
default: "7d",
},
description: "Time range for statistics",
},
description: "Time range for statistics",
},
],
],
responses: {
200: {
description: "Statistics fetched successfully",
},
401: {
description: "Unauthorized",
responses: {
200: {
description: "Statistics fetched successfully",
},
401: {
description: "Unauthorized",
},
},
},
},
},
"/api/statictics/{id}": {
@@ -136,6 +136,3 @@
},
},
};
@@ -1,10 +0,0 @@
import { z } from "zod";
const create_statictics = z.object({});
const update_statictics = z.object({});
export const statictics_validations = {
create_statictics,
update_statictics,
};
+21 -37
View File
@@ -1,14 +1,17 @@
import { Request, Response } from "express";
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: Request, res: Response) => {
const id = req?.user?.accountId;
const data = {
...req.body,
storeAccountId: id as string,
};
storeAccountId: id as string
}
const result = await support_service.createSupportIntoDB(data);
manageResponse(res, {
@@ -20,43 +23,31 @@ const createSupport = catchAsync(async (req: Request, res: Response) => {
});
});
const getAllSupport = catchAsync(async (req: Request, res: Response) => {
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 page = Number(req?.query?.page) || 1;
const limit = Number(req?.query?.limit) || 10;
const result = await support_service.getAllSupportFromDB(
id as string,
role as string,
search as string,
type as string,
status as string,
page,
limit
);
const result = await support_service.getAllSupportFromDB(id as string, role as string, search as string, type as string, status as string);
manageResponse(res, {
success: true,
statusCode: 200,
message: "All support fetched successfully.",
data: result.data,
meta: result.meta,
data: result,
meta: {},
});
});
const get_single_support = catchAsync(async (req, res) => {
const { id } = req.params;
const {id} = req.params;
const userId = req?.user?.accountId;
const role = req?.user?.role;
const role = req?.user?.role
const result = await support_service.getSingleSupportFromDB(
id as string,
userId as string,
role as string,
);
const result = await support_service.getSingleSupportFromDB(id as string, userId as string, role as string);
manageResponse(res, {
success: true,
statusCode: 200,
@@ -66,18 +57,15 @@ const get_single_support = catchAsync(async (req, res) => {
});
});
const update_support = catchAsync(async (req, res) => {
const { id } = req.params;
const {id} = req.params;
const userId = req?.user?.accountId;
const role = req?.user?.role;
const data = req.body;
const data = req.body
const result = await support_service.updateSupportIntoDB(
id as string,
userId as string,
role as string,
data,
);
const result = await support_service.updateSupportIntoDB(id as string, userId as string, role as string, data);
manageResponse(res, {
success: true,
statusCode: 200,
@@ -88,15 +76,11 @@ const update_support = catchAsync(async (req, res) => {
});
const delete_support = catchAsync(async (req, res) => {
const { id } = req.params;
const {id} = req.params;
const userId = req?.user?.accountId;
const role = req?.user?.role;
const role = req?.user?.role
const result = await support_service.deleteSupportFromDB(
id as string,
userId as string,
role as string,
);
const result = await support_service.deleteSupportFromDB(id as string, userId as string, role as string);
manageResponse(res, {
success: true,
statusCode: 200,
+9 -28
View File
@@ -13,8 +13,6 @@ const getAllSupportFromDB = async (
search?: string,
type?: string,
status?: string,
page: number = 1,
limit: number = 10,
) => {
const andCondition: Prisma.SupportWhereInput[] = [];
@@ -57,31 +55,14 @@ const getAllSupportFromDB = async (
const whereCondition: Prisma.SupportWhereInput =
andCondition.length > 0 ? { AND: andCondition } : {};
const skip = (page - 1) * limit;
const [data, total] = await Promise.all([
prisma.support.findMany({
where: whereCondition,
skip,
take: limit,
orderBy: {
createdAt: "desc",
},
}),
prisma.support.count({
where: whereCondition,
}),
]);
return {
meta: {
page,
limit,
total,
totalPage: Math.ceil(total / limit),
const result = await prisma.support.findMany({
where: whereCondition,
orderBy: {
createdAt: "desc",
},
data,
};
});
return result;
};
const getSingleSupportFromDB = async (
@@ -144,8 +125,8 @@ const deleteSupportFromDB = async (
}
const result = await prisma.support.delete({
where: { id },
});
where: {id}
})
return result;
};
+11 -58
View File
@@ -1,18 +1,18 @@
export const supportSwaggerDocs = {
export const supportSwaggerDocs = {
"/api/support": {
post: {
tags: ["support"],
summary: "Create new support",
description:
"type must be: TECHNICAL | BILLING | DOMAIN | TEMPLATE | PAYMENT | ACCOUNT | FEATURE_REQUEST | BUG | OTHER",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({
issueName: "Your issue name",
description: "Issue description",
type: "Issue Type",
"issueName": "Your issue name",
"description": "Issue description",
"type": "Issue Type"
}), // put your request body
},
},
@@ -39,45 +39,6 @@ export const supportSwaggerDocs = {
required: false,
schema: { type: "number" },
},
{
name: "search",
in: "query",
required: false,
description: "Search by issue name or description",
schema: {
type: "string",
},
},
{
name: "type",
in: "query",
required: false,
description: "Filter by support type",
schema: {
type: "string",
enum: [
"TECHNICAL",
"BILLING",
"DOMAIN",
"TEMPLATE",
"PAYMENT",
"ACCOUNT",
"FEATURE_REQUEST",
"BUG",
"OTHER",
],
},
},
{
name: "status",
in: "query",
required: false,
description: "Filter by support status",
schema: {
type: "string",
enum: ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"],
},
},
],
responses: {
200: { description: "support fetched successfully" },
@@ -107,11 +68,7 @@ export const supportSwaggerDocs = {
patch: {
tags: ["support"],
summary: "Update support",
description: `type(enum): TECHNICAL | BILLING | DOMAIN | TEMPLATE | PAYMENT | ACCOUNT | FEATURE_REQUEST | BUG | OTHER \n
status(enum): OPEN
| IN_PROGRESS
| RESOLVED
| REJECTED`,
description: "",
parameters: [
{
name: "id",
@@ -124,14 +81,7 @@ export const supportSwaggerDocs = {
required: true,
content: {
"application/json": {
example: JSON.stringify({
issueName: "Your issue name",
description: "Issue description",
type: "Issue Type",
status: "issue current status",
resolvedBy: "dataTime()",
resolvedAt: "dateTime()",
}), // put your request body
example: JSON.stringify({}), // put your request body
},
},
},
@@ -159,3 +109,6 @@ export const supportSwaggerDocs = {
},
},
};
+69
View File
@@ -0,0 +1,69 @@
import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response.js";
import { users_service } from "./users.service.js";
const get_all_users = catchAsync(async (req, res) => {
const result = await users_service.get_all_users_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "All users fetched successfully.",
data: result,
meta: {},
});
});
const get_single_users = catchAsync(async (req, res) => {
const result = await users_service.get_single_users_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "Single users fetched successfully.",
data: result,
meta: {},
});
});
const create_users = catchAsync(async (req, res) => {
const result = await users_service.create_users_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "users created successfully.",
data: result,
meta: {},
});
});
const update_users = catchAsync(async (req, res) => {
const result = await users_service.update_users_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "users updated successfully.",
data: result,
meta: {},
});
});
const delete_users = catchAsync(async (req, res) => {
const result = await users_service.delete_users_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "users deleted successfully.",
data: result,
meta: {},
});
});
export const users_controller = {
get_all_users,
get_single_users,
create_users,
update_users,
delete_users,
};
+24
View File
@@ -0,0 +1,24 @@
import { Router } from "express";
import { users_controller } from "./users.controller.js";
import { users_validations } from "./users.validation.js";
import RequestValidator from "../../middlewares/request_validator.js";
const router = Router();
router.get("/", users_controller.get_all_users);
router.post(
"/",
RequestValidator(users_validations.create_users),
users_controller.create_users,
);
router.get("/:id", users_controller.get_single_users);
router.patch(
"/:id",
RequestValidator(users_validations.update_users),
users_controller.update_users,
);
router.delete("/:id", users_controller.delete_users);
export default router;
+70
View File
@@ -0,0 +1,70 @@
import { Request } from "express";
import { prisma } from "../../lib/prisma.js";
import paginationHelper from "../../utils/pagination_helper.js";
const get_all_users_from_db = async (req: Request) => {
const { page, skip, limit } = paginationHelper(req.query);
const search = req.query.search as string;
const andCondition = {} as any;
if (search) {
andCondition.shopName = {
contains: search,
mode: "insensitive",
};
}
// define your own login here
const result = await prisma.profile.findMany({
take: limit,
skip,
where: andCondition,
select: {
account: {
select: {
isSubscribe: true,
email: true,
},
},
shopName: true,
id: true,
status: true,
},
});
const usersCount = await prisma.profile.count();
return { result, usersCount, page, limit, skip };
};
const get_single_users_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.profile.findUnique({ where: { id } });
return result;
};
const create_users_into_db = async (req: Request) => {
// define your own login here
const result = await prisma.account.create({ data: req.body });
return result;
};
const update_users_into_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.account.update({ where: { id }, data: req.body });
return result;
};
const delete_users_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.account.delete({ where: { id } });
return result;
};
export const users_service = {
get_all_users_from_db,
get_single_users_from_db,
create_users_into_db,
update_users_into_db,
delete_users_from_db,
};
+112
View File
@@ -0,0 +1,112 @@
export const usersSwaggerDocs = {
"/api/users": {
post: {
tags: ["users"],
summary: "Create new users",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({}), // put your request body
},
},
},
responses: {
201: { description: "users created successfully" },
500: { description: "Validation error or internal server error" },
},
},
get: {
tags: ["users"],
summary: "Get all users",
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" },
},
],
responses: {
200: { description: "users fetched successfully" },
401: { description: "unauthorized" },
},
},
},
"/api/users/{id}": {
get: {
tags: ["users"],
summary: "Get single users",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "users fetched successfully" },
401: { description: "unauthorized" },
},
},
patch: {
tags: ["users"],
summary: "Update users",
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: "users updated successfully" },
500: { description: "Validation error or internal server error" },
},
},
delete: {
tags: ["users"],
summary: "Delete users",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "users delete successfully" },
401: { description: "unauthorized" },
},
},
},
};
+10
View File
@@ -0,0 +1,10 @@
import { z } from "zod";
const create_users = z.object({});
const update_users = z.object({});
export const users_validations = {
create_users,
update_users,
};
+3 -5
View File
@@ -4,16 +4,14 @@ 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";
import templateRoute from "./app/modules/template/template.route.js";
import { staticticsRoute } from "./app/modules/statictics/statictics.route.js";
import analyticsRoute from "./app/modules/analytics/analytics.route.js";
import templateRoute from "./app/modules/template/template.route";
import usersRoute from "./app/modules/users/users.route";
const appRouter = Router();
const moduleRoutes = [
{ path: "/admin/analytics", route: analyticsRoute },
{ path: "/users", route: usersRoute },
{ path: "/template", route: templateRoute },
{ path: "/statictics", route: staticticsRoute },
{ path: "/order", route: orderRoute },
{ path: "/support", route: supportRoute },
{ path: "/plan", route: planRoute },
+3 -5
View File
@@ -6,9 +6,8 @@ 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";
import { templateSwaggerDocs } from "./app/modules/template/template.swagger.js";
import { staticticsSwaggerDocs } from "./app/modules/statictics/statictics.swagger.js";
import { analyticsSwaggerDocs } from "./app/modules/analytics/analytics.swagger";
import { templateSwaggerDocs } from "./app/modules/template/template.swagger";
import { usersSwaggerDocs } from "./app/modules/users/users.swagger";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -28,8 +27,7 @@ export const swaggerOptions = {
...profileSwaggerDocs,
...supportSwaggerDocs,
...templateSwaggerDocs,
...staticticsSwaggerDocs,
...analyticsSwaggerDocs,
...usersSwaggerDocs,
},
servers:
configs.env === "production"