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
@@ -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,
};