Compare commits
6 Commits
49cb339e5a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bf4bc93f1 | |||
| f224ff6bf0 | |||
| 4468c00abf | |||
| 0a8fe573ae | |||
| c05266a522 | |||
| 1abecc9b8f |
Vendored
-37
@@ -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;
|
||||
Vendored
-26
@@ -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,
|
||||
};
|
||||
Vendored
-15
@@ -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;
|
||||
Vendored
-8
@@ -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 };
|
||||
Vendored
-23
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
Vendored
-12
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
Vendored
-12
@@ -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
@@ -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 don’t have permission to create plan information.!!!", 401);
|
||||
}
|
||||
const result = await prisma.plan.create({ data: req.body });
|
||||
return result;
|
||||
};
|
||||
const update_plan_into_db = async (req) => {
|
||||
// define your own login here
|
||||
const { id } = req.params;
|
||||
const user = req.user;
|
||||
if (user?.role !== "ADMIN") {
|
||||
throw new AppError("You don’t have permission to update plan information.!!!", 401);
|
||||
}
|
||||
const isPlanExist = await prisma.plan.findFirst({
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
if (!isPlanExist) {
|
||||
throw new AppError("The plan is not available!!!", 404);
|
||||
}
|
||||
const result = await prisma.plan.update({
|
||||
where: {
|
||||
id: isPlanExist.id
|
||||
},
|
||||
data: {
|
||||
...req.body
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
const delete_plan_from_db = async (req) => {
|
||||
// define your own login here
|
||||
const { id } = req.params;
|
||||
const user = req.user;
|
||||
if (user?.role !== "ADMIN") {
|
||||
throw new AppError("You don’t have permission to delete plan information.!!!", 401);
|
||||
}
|
||||
const result = await prisma.plan.delete({ where: { id: id } });
|
||||
return result;
|
||||
};
|
||||
export const plan_service = {
|
||||
get_all_plan_from_db,
|
||||
get_single_plan_from_db,
|
||||
create_plan_into_db,
|
||||
update_plan_into_db,
|
||||
delete_plan_from_db,
|
||||
};
|
||||
-125
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import { z } from "zod";
|
||||
const update_profile = z.object({
|
||||
fullName: z.string().optional(),
|
||||
});
|
||||
export const profile_validations = {
|
||||
update_profile,
|
||||
};
|
||||
-80
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
Vendored
-7
@@ -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
@@ -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);
|
||||
};
|
||||
Vendored
-6
@@ -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
@@ -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,
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Queue } from "bullmq";
|
||||
import { redisConnection } from "../../connection.js";
|
||||
export const orderEmailQueue = new Queue("order-email-queue", {
|
||||
connection: redisConnection,
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
import "./email/email.worker.js";
|
||||
import "./email/order/order.email.worker.js";
|
||||
console.log("Workers running...");
|
||||
Vendored
-80
@@ -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>
|
||||
`;
|
||||
};
|
||||
Vendored
-15
@@ -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,
|
||||
};
|
||||
Vendored
-13
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-11
@@ -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;
|
||||
Vendored
-23
@@ -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;
|
||||
Vendored
-113
@@ -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;
|
||||
"
|
||||
>
|
||||
© 2026 to {{year}} Quick Launch. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
`,
|
||||
});
|
||||
return info;
|
||||
};
|
||||
export default sendMail;
|
||||
Vendored
-9
@@ -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;
|
||||
Vendored
-23
@@ -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;
|
||||
};
|
||||
Vendored
-15
@@ -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;
|
||||
Vendored
-16
@@ -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;
|
||||
Vendored
-19
@@ -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);
|
||||
});
|
||||
Vendored
-48
@@ -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"),
|
||||
],
|
||||
};
|
||||
@@ -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';
|
||||
@@ -1,3 +1,10 @@
|
||||
enum ShopStatus {
|
||||
ACTIVE
|
||||
SUSPENDED
|
||||
DELETED
|
||||
INACTIVE
|
||||
}
|
||||
|
||||
model Profile {
|
||||
id String @id @default(uuid())
|
||||
accountId String @unique
|
||||
@@ -8,6 +15,5 @@ model Profile {
|
||||
shopAddress String?
|
||||
shopMapLocation String?
|
||||
shopCategory String?
|
||||
status ShopStatus @default(ACTIVE)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -13,7 +13,7 @@ export const profileSwaggerDocs = {
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
fullName: { type: "string" },
|
||||
shopName: { type: "string" },
|
||||
},
|
||||
},
|
||||
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": {
|
||||
@@ -70,7 +71,6 @@
|
||||
description: "Unauthorized",
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
const result = await 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),
|
||||
},
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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
@@ -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 },
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user