init: init project

This commit is contained in:
2026-04-02 21:27:09 +06:00
commit 81f9801487
45 changed files with 1857 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
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,
},
};
+20
View File
@@ -0,0 +1,20 @@
import { ZodError, ZodIssue } from 'zod'
import { TErrorSources, TGenericErrorResponse } from '../types/error'
const handleZodError = (err: ZodError): TGenericErrorResponse => {
const errorSources: TErrorSources = err.issues.map((issue: ZodIssue) => {
return {
path: issue?.path[issue.path.length - 1] as string,
message: issue.message
}
})
const statusCode = 400
return {
statusCode,
message: 'Validation Error',
errorSources
}
}
export default handleZodError
+10
View File
@@ -0,0 +1,10 @@
import { PrismaPg } from "@prisma/adapter-pg";
import "dotenv/config";
import { PrismaClient } from "../../../prisma/generated/prisma/client";
const connectionString = `${process.env.DATABASE_URL}`;
const adapter = new PrismaPg({ connectionString });
const prisma = new PrismaClient({ adapter });
export { prisma };
+30
View File
@@ -0,0 +1,30 @@
import { NextFunction, Request, Response } from "express";
import { configs } from "../configs";
import { AppError } from "../utils/app_error";
import { jwtHelpers, JwtPayloadType } from "../utils/JWT";
type Role = "ADMIN" | "USER";
const auth = (...roles: Role[]) => {
return async (req: Request, res: Response, next: NextFunction) => {
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 as string,
);
if (!roles.length || !roles.includes(verifiedUser.role)) {
throw new AppError("You are not authorize!!", 401);
}
req.user = verifiedUser as JwtPayloadType;
next();
} catch (err) {
next(err);
}
};
};
export default auth;
@@ -0,0 +1,51 @@
import { ErrorRequestHandler } from "express";
import { ZodError } from "zod";
import { configs } from "../configs";
import handleZodError from "../errors/zodError";
import { TErrorSources } from "../types/error";
import { AppError } from "../utils/app_error";
const globalErrorHandler: ErrorRequestHandler = (err, req, res, next) => {
let statusCode = 500;
let message = "Something went wrong!";
let errorSources: TErrorSources = [
{
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;
+10
View File
@@ -0,0 +1,10 @@
import { Request, Response, NextFunction } from 'express';
const notFound = (req: Request, res: Response, next: NextFunction) => {
res.status(404).json({
message: 'Sorry Route is not found!! 😴😴😴',
success: false,
error: '',
});
};
export default notFound;
+14
View File
@@ -0,0 +1,14 @@
import { NextFunction, Request, Response } from "express";
const RequestValidator = (schema: any) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
req.body = await schema.parseAsync(req.body);
next();
} catch (err) {
next(err);
}
};
};
export default RequestValidator;
+16
View File
@@ -0,0 +1,16 @@
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;
@@ -0,0 +1,116 @@
import { configs } from "../../configs";
import catchAsync from "../../utils/catch_async";
import manageResponse from "../../utils/manage_response";
import { account_services } from "./account.service";
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 successfull",
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 successfull",
data: result,
});
});
const login_user = catchAsync(async (req, res) => {
const result = await account_services.login_user_into_db(req);
// set access token into cookie
res.cookie("access_token", result, {
secure: configs.env === "production",
httpOnly: true,
});
manageResponse(res, {
statusCode: 200,
success: true,
message: "User logged in successfully",
data: {
accessToken: result,
},
});
});
const get_user_account = catchAsync(async (req, res) => {
const result = await account_services.get_user_account_from_db(req);
manageResponse(res, {
statusCode: 200,
success: true,
message: "Account fetched successfully",
data: result,
});
});
const change_password = catchAsync(async (req, res) => {
const result = await account_services.change_password_into_db(req);
manageResponse(res, {
statusCode: 200,
success: true,
message: "Password Change successfully",
data: result,
});
});
const resend_otp_and_verification_link = catchAsync(async (req, res) => {
const result =
await account_services.resend_otp_and_verification_link_from_db(req);
manageResponse(res, {
statusCode: 200,
success: true,
message: "OTP reset successfully",
data: result,
});
});
const forget_password_genereate_reset_token = catchAsync(async (req, res) => {
const result =
await account_services.forget_password_genereate_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_genereate_reset_token,
reset_password_using_token
};
+55
View File
@@ -0,0 +1,55 @@
import { Router } from "express";
import auth from "../../middlewares/auth";
import RequestValidator from "../../middlewares/request_validator";
import { account_controller } from "./account.controller";
import { account_validation } from "./account.validation";
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_genereate_reset_token,
);
accountRouter.put(
"/reset-password",
RequestValidator(account_validation.reset_pass),
account_controller.reset_password_using_token,
);
export default accountRouter;
+402
View File
@@ -0,0 +1,402 @@
import bcrypt from "bcrypt";
import { Request } from "express";
import { configs } from "../../configs";
import { prisma } from "../../lib/prisma";
import { AppError } from "../../utils/app_error";
import { jwtHelpers } from "../../utils/JWT";
import { otpGenerator } from "../../utils/otpGenerator";
import sendMail from "../../utils/mail_sender";
const create_account_into_db = async (req: Request) => {
const payload = req?.body;
// 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: {
fullName: payload.fullName,
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 as string,
"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 sendMail({
to: payload.email as string,
subject: "welcome to - Please verify your account",
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",
name: payload.fullName,
});
return result;
};
const verify_account_using_otp_into_db = async (req: Request) => {
const payload: { email: string; otp: string } = 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: Request) => {
const token = req?.body?.token as string;
let decoadeToken: any;
try {
decoadeToken = jwtHelpers.verifyToken(
token,
configs.jwt.verified_token as string,
);
} catch (error: any) {
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: Request) => {
const payload = req?.body;
const account = await prisma.account.findUnique({
where: {
email: payload.email,
},
});
// check if account exists
if (!account) {
throw new AppError("Account not found", 404);
}
// checking password
const isPasswordMatch = bcrypt.compareSync(
payload.password,
account.password,
);
if (!isPasswordMatch) {
throw new AppError("Invalid password", 401);
}
// check if account is deleted
if (account.isDeleted) {
throw new AppError("Account is deleted", 401);
}
// check if account is verified
if (!account.isAccountVerified) {
throw new AppError("Account is not verified", 401);
}
// generate access
const accessToken = jwtHelpers.generateToken(
{
email: account.email,
role: account.role,
accountId: account.id,
},
configs.jwt.access_token as string,
configs.jwt.access_expires as string,
);
return accessToken;
};
const get_user_account_from_db = async (req: Request) => {
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: Request) => {
const user = req?.user;
// payload
const payload: { oldPassword: string; newPassword: string } = 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: Request) => {
const email = req?.body?.email as string;
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 as string,
"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 as string,
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_genereate_reset_token_from_db = async (req: Request) => {
const email = req?.body?.email as string;
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 as string,
"5m",
);
const verificationLink = `${configs.jwt.front_end_url}/verify/token?=${verificationToken}`;
await sendMail({
to: email as string,
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: Request) => {
const token = req?.body?.token as string;
const newPass = req?.body?.newPass as string;
let decoadeToken: any;
try {
decoadeToken = jwtHelpers.verifyToken(
token,
configs.jwt.verified_token as string,
);
} catch (error: any) {
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_genereate_reset_token_from_db,
reset_password_using_token_into_db
};
+187
View File
@@ -0,0 +1,187 @@
export const accountSwaggerDocs = {
"/api/auth/sign-up": {
post: {
tags: ["account"],
summary: "Create new account",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({
email: "user@gmail.com",
password: "password",
fullName: "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" },
},
},
},
};
@@ -0,0 +1,41 @@
import z from "zod";
const sign_up = z.object({
email: z.string("Email is required."),
password: z.string("Password is required."),
fullName: 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 requied"),
newPassword: z.string("New Password is required"),
});
const verify_otp = z.object({
email: z.string("Email is requied"),
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 requied"),
});
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
};
@@ -0,0 +1,17 @@
import catchAsync from "../../utils/catch_async";
import manageResponse from "../../utils/manage_response";
import { profile_service } from "./profile.service";
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,
};
+22
View File
@@ -0,0 +1,22 @@
import { Router } from "express";
import RequestValidator from "../../middlewares/request_validator";
import { profile_controller } from "./profile.controller";
import { profile_validations } from "./profile.validation";
import auth from "../../middlewares/auth";
import uploader from "../../middlewares/uploader";
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;
@@ -0,0 +1,26 @@
import { Request } from "express";
import uploadCloud from "../../utils/cloudinary";
import { prisma } from "../../lib/prisma";
import { JwtPayloadType } from "../../utils/JWT";
const update_profile_into_db = async (req: Request) => {
const user = req?.user as JwtPayloadType;
const payload = req?.body;
const file = req?.file;
// check file and upload to cloud
if (file) {
const cloudRes = await uploadCloud(file);
payload.profilePhoto = cloudRes?.secure_url;
}
const result = await prisma.profile.update({
where: {
accountId: user.accountId as string,
},
data: payload,
});
return result;
};
export const profile_service = {
update_profile_into_db,
};
@@ -0,0 +1,34 @@
export const profileSwaggerDocs = {
"/api/profile": {
patch: {
tags: ["profile"],
summary: "Update profile",
requestBody: {
required: true,
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: {
data: {
type: "object",
properties: {
fullName: { type: "string" },
},
},
file: {
type: "string",
format: "binary",
},
},
},
},
},
},
responses: {
200: { description: "profile updated successfully" },
500: { description: "Validation error or internal server error" },
},
},
},
};
@@ -0,0 +1,8 @@
import { z } from "zod";
const update_profile = z.object({
fullName: z.string().optional(),
});
export const profile_validations = {
update_profile,
};
+10
View File
@@ -0,0 +1,10 @@
export type TErrorSources = {
path: string | number
message: string
}[]
export type TGenericErrorResponse = {
statusCode: number
message: string
errorSources: TErrorSources
}
+12
View File
@@ -0,0 +1,12 @@
import { TJwtUser } from "../modules/auth/auth.interface";
import { JwtPayloadType } from "../utils/JWT";
declare global {
namespace Express {
interface Request {
user?: JwtPayloadType;
}
}
}
+27
View File
@@ -0,0 +1,27 @@
import jwt, { JwtPayload, Secret, SignOptions } from "jsonwebtoken";
const generateToken = (payload: object, secret: Secret, expiresIn: string) => {
const token = jwt.sign(payload, secret, {
algorithm: "HS256",
expiresIn: expiresIn,
} as SignOptions);
return token;
};
const verifyToken = (token: string, secret: Secret): JwtPayload => {
return jwt.verify(token, secret) as JwtPayload;
};
export const jwtHelpers = {
generateToken,
verifyToken,
};
export type JwtPayloadType = JwtPayload & {
email: string;
role: string;
accountId: string;
iat: number;
exp: number;
};
export type JwtTokenType = string | JwtPayloadType | null;
+12
View File
@@ -0,0 +1,12 @@
export class AppError extends Error {
public statusCode: number;
constructor(message: string, statusCode: number, stack = '') {
super(message);
this.statusCode = statusCode;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
import { RequestHandler, Request, Response, NextFunction } from 'express';
const catchAsync = (fn: RequestHandler): RequestHandler => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
await fn(req, res, next);
} catch (error) {
next(error);
}
};
};
export default catchAsync;
+66
View File
@@ -0,0 +1,66 @@
import { v2 as cloudinary } from 'cloudinary';
import fs from 'fs';
import { configs } from '../configs';
type ICloudinaryResponse = {
asset_id: string;
public_id: string;
version: number;
version_id: string;
signature: string;
width: number;
height: number;
format: string;
resource_type: string;
created_at: string;
tags: string[];
bytes: number;
type: string;
etag: string;
placeholder: boolean;
url: string;
secure_url: string;
folder: string;
overwritten: boolean;
original_filename: string;
original_extension: string;
api_key: string;
};
type IFile = {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
destination: string;
filename: string;
path: string;
size: number;
};
// 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: IFile
): Promise<ICloudinaryResponse | undefined> => {
return new Promise((resolve, reject) => {
cloudinary.uploader.upload(
file.path,
(error: Error, result: ICloudinaryResponse) => {
fs.unlinkSync(file.path);
if (error) {
reject(error);
} else {
resolve(result);
}
}
);
});
};
export default uploadCloud;
+102
View File
@@ -0,0 +1,102 @@
import nodemailer from 'nodemailer';
import { configs } from '../configs';
type TMailContent = {
to: string,
subject: string,
textBody: string,
htmlBody: string,
name?: string
}
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: TMailContent) => {
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://imgs.search.brave.com/IZoN38NQxnIIuB1I9E70bW6q5OvbEtz68YaxTe1j-o0/rs:fit:860:0:0:0/g:ce/aHR0cHM6Ly9lbGVt/ZW50cy1yZXNpemVk/LmVudmF0b3VzZXJj/b250ZW50LmNvbS9l/bGVtZW50cy1jb3Zl/ci1pbWFnZXMvMjhi/NmVjMTQtMGMwOS00/NGY1LWE5NGUtNmIy/OTM5NTZkMDM2P3c9/NDMzJmNmX2ZpdD1z/Y2FsZS1kb3duJnE9/ODUmZm9ybWF0PWF1/dG8mcz04Mjc0OWYy/ZDUyMmJiM2NlMjNi/OWNhNjhlZmFhNjdk/MTg5OGI4NWIwNzBh/MjQ1NjM4NmI1ZmFj/NWVmNmM5ZTNl"
alt="">
<p style="font-size: 12px;">The Support Team</p>
<h3>Company Name</h3>
</div>
</div>
<p style="font-size: 14px; color: #999999; margin-top: 20px; margin-bottom: 10px; text-align: center;">
This is an automated message — please do not reply to this email.
<br>
If you need assistance, feel free to contact our support team.
<br><br>
Thank you for choosing us!
</p>
<hr>
<div style="text-align: center; font-size: 12px; color: #999999; margin-top: 20px;">
&copy; {{year}} Your Company. All rights reserved.
</div>
</div>
</body>
</html>
`,
});
return info
};
export default sendMail;
+24
View File
@@ -0,0 +1,24 @@
import { Response } from "express"
interface IResponse<T> {
success: boolean,
statusCode: number,
message: string,
data?: T,
meta?: {
page?: number,
limit?: number,
skip?: number,
total?: number
}
}
const manageResponse = <T>(res: Response, payload: IResponse<T>) => {
res.status(payload.statusCode).json({
success: payload.success,
message: payload.message,
data: payload.data || undefined || null,
meta: payload.meta || undefined || null
})
}
export default manageResponse
+29
View File
@@ -0,0 +1,29 @@
export const otpGenerator = (): string => {
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;
};