Skip to content

Commit 7e3f366

Browse files
authored
refactor: ♻️ signup user with local account (#18)
* refactor: ♻️ signup user with local account * feat: ✨ send otp to user email when he signup
1 parent bbac1eb commit 7e3f366

25 files changed

+3028
-260
lines changed

.github/ISSUE_TEMPLATE/refactor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: "♻ Refactor Code"
22
description: "Refactor your code"
33
title: "[REFACTOR] :recycle: <title>"
4-
labels: [refactor"]
4+
labels: ["refactor"]
55
body:
66
- type: textarea
77
id: description

package.json

+49-43
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,51 @@
11
{
2-
"dependencies": {
3-
"@prisma/client": "^4.3.1",
4-
"@typegoose/typegoose": "^9.12.1",
5-
"@types/dotenv": "^8.2.0",
6-
"bcryptjs": "^2.4.3",
7-
"body-parser": "^1.20.0",
8-
"cookie-session": "^2.0.0",
9-
"cors": "^2.8.5",
10-
"dotenv": "^16.0.2",
11-
"express": "^4.18.1",
12-
"express-session": "^1.17.3",
13-
"http-status-codes": "^2.2.0",
14-
"jsonwebtoken": "^8.5.1",
15-
"mongoose": "^6.6.1",
16-
"nodemon": "^2.0.20",
17-
"passport": "0.6.0",
18-
"passport-google-oauth20": "^2.0.0",
19-
"pino": "^8.6.0",
20-
"pino-pretty": "^9.1.0",
21-
"prisma": "^4.3.1"
22-
},
23-
"devDependencies": {
24-
"@types/bcryptjs": "2.4.2",
25-
"@types/cookie-session": "2.0.44",
26-
"@types/cors": "2.8.12",
27-
"@types/express": "4.17.14",
28-
"@types/express-session": "1.17.5",
29-
"@types/jsonwebtoken": "8.5.9",
30-
"@types/passport": "1.0.11",
31-
"@types/passport-google-oauth20": "2.0.11",
32-
"add": "2.0.6",
33-
"ts-node": "10.9.1",
34-
"typescript": "4.8.4",
35-
"yarn": "1.22.19"
36-
},
37-
"name": "multiemail-backend",
38-
"version": "1.0.0",
39-
"main": "src/app.ts",
40-
"author": "btsp",
41-
"license": "MIT",
42-
"scripts": {
43-
"dev": "nodemon src/app.ts"
44-
}
2+
"name": "multiemail-backend",
3+
"version": "1.0.0",
4+
"main": "src/app.ts",
5+
"author": "btsp",
6+
"license": "MIT",
7+
"scripts": {
8+
"dev": "tsnd --exit-child --transpile-only src/app.ts"
9+
},
10+
"dependencies": {
11+
"@prisma/client": "^4.3.1",
12+
"@typegoose/typegoose": "^9.12.1",
13+
"@types/lodash": "^4.14.186",
14+
"@types/nodemailer": "^6.4.6",
15+
"bcrypt": "^5.0.1",
16+
"body-parser": "^1.20.0",
17+
"cookie-session": "^2.0.0",
18+
"cors": "^2.8.5",
19+
"dotenv": "^16.0.2",
20+
"express": "^4.18.1",
21+
"express-session": "^1.17.3",
22+
"http-status-codes": "^2.2.0",
23+
"jsonwebtoken": "^8.5.1",
24+
"lodash": "^4.17.21",
25+
"mongoose": "^6.6.1",
26+
"nodemailer": "^6.8.0",
27+
"nodemon": "^2.0.20",
28+
"passport": "0.6.0",
29+
"passport-google-oauth20": "^2.0.0",
30+
"pino": "^8.6.0",
31+
"pino-pretty": "^9.1.0",
32+
"prisma": "^4.3.1",
33+
"zod": "^3.19.1"
34+
},
35+
"devDependencies": {
36+
"@types/bcrypt": "^5.0.0",
37+
"@types/cookie-session": "2.0.44",
38+
"@types/cors": "2.8.12",
39+
"@types/dotenv": "^8.2.0",
40+
"@types/express": "4.17.14",
41+
"@types/express-session": "1.17.5",
42+
"@types/jsonwebtoken": "8.5.9",
43+
"@types/passport": "1.0.11",
44+
"@types/passport-google-oauth20": "2.0.11",
45+
"add": "2.0.6",
46+
"ts-node": "10.9.1",
47+
"ts-node-dev": "^2.0.0",
48+
"typescript": "4.8.4",
49+
"yarn": "1.22.19"
50+
}
4551
}

src/app.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ app.use(passport.session());
3939

4040
app.use("/api", authRouter);
4141

42-
mongoose.connect(process.env.DB_URI, () => {
42+
mongoose.connect(process.env.DB_URI as string, () => {
4343
const PORT = process.env.PORT || 3001;
4444

4545
logger.info("Connected to Database!");

src/controllers/auth.controller.ts

+56-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,58 @@
1-
import { Request, Response } from 'express';
2-
import { StatusCodes } from 'http-status-codes';
3-
4-
export function logoutHandler(req: Request, res: Response) {
5-
req.session = null;
6-
req.logout((err) => {
7-
if (err)
8-
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
9-
error: 'Internal server error',
10-
});
11-
});
12-
}
1+
import { Request, Response } from "express";
2+
import { StatusCodes } from "http-status-codes";
3+
import { SignupSchema } from "../schemas/auth.schema";
4+
import {
5+
createUserService,
6+
findUserByEitherEmailOrUsernameService,
7+
} from "../services/user.service";
8+
import { sendEmail } from "../util/email.util";
9+
import logger from "../util/logger.util";
10+
11+
export async function signupHandler(
12+
req: Request<{}, {}, SignupSchema["body"]>,
13+
res: Response
14+
) {
15+
const { username, email, password } = req.body;
16+
17+
try {
18+
const existingUser = await findUserByEitherEmailOrUsernameService(
19+
email,
20+
username
21+
);
22+
23+
if (existingUser) {
24+
return res.status(StatusCodes.CONFLICT).json({
25+
error: "User with same email or username already exists",
26+
});
27+
}
28+
29+
const createdUser = await createUserService({
30+
username,
31+
email,
32+
password,
33+
role: "user",
34+
});
35+
36+
sendEmail(
37+
email,
38+
"OTP for Multi Email",
39+
`Welcome to Multi Email your OTP is ${createdUser.verificationCode}`
40+
);
41+
42+
return res.status(StatusCodes.CREATED).json({
43+
message: "User created successfully",
44+
});
45+
} catch (err: any) {
46+
logger.error(err);
47+
48+
if (err.code === 11000) {
49+
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({
50+
error: "User with same email or username already exists",
51+
});
52+
}
1353

14-
export function isLoggedin(req, res, next) {
15-
req.user ? next() : res.sendStatus(401);
54+
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
55+
error: "Internal server error",
56+
});
57+
}
1658
}

src/middleware/requireLogin.middleware.ts

-19
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { NextFunction, Request, Response } from "express";
2+
import { StatusCodes } from "http-status-codes";
3+
import { AnyZodObject, ZodError } from "zod";
4+
5+
/**
6+
* This middleware will validate the request body using zod schema
7+
* @param schema schema created using zod
8+
*/
9+
function validateRequest(schema: AnyZodObject) {
10+
return (req: Request, res: Response, next: NextFunction) => {
11+
try {
12+
schema.parse({ body: req.body, query: req.query, params: req.params });
13+
14+
return next();
15+
} catch (err) {
16+
if (err instanceof ZodError) {
17+
switch (err.errors[0].code) {
18+
case "invalid_string":
19+
res.status(StatusCodes.UNPROCESSABLE_ENTITY);
20+
break;
21+
22+
case "invalid_type":
23+
res.status(StatusCodes.BAD_REQUEST);
24+
break;
25+
26+
case "invalid_enum_value":
27+
res.status(StatusCodes.UNPROCESSABLE_ENTITY);
28+
break;
29+
30+
case "custom":
31+
res.status(StatusCodes.UNAUTHORIZED);
32+
break;
33+
34+
default:
35+
res.status(StatusCodes.BAD_REQUEST);
36+
break;
37+
}
38+
return res.json({
39+
error: err.errors[0].message,
40+
});
41+
}
42+
43+
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
44+
error: "Internal server error",
45+
});
46+
}
47+
};
48+
}
49+
50+
export default validateRequest;

src/models/counter.model.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { prop, getModelForClass } from "@typegoose/typegoose";
2+
3+
export class Counter {
4+
@prop({ required: true, unique: true })
5+
public _id: string;
6+
7+
@prop({ required: true })
8+
public sequence_value: number;
9+
}
10+
11+
const CounterModel = getModelForClass(Counter, {
12+
schemaOptions: { timestamps: true },
13+
});
14+
15+
export default CounterModel;

src/models/jwt.model.ts

-17
This file was deleted.

src/models/session.model.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { getModelForClass, prop, Ref } from "@typegoose/typegoose";
2+
import { User } from "./user.model";
3+
4+
export class Session {
5+
@prop({ required: true, ref: () => User })
6+
user: Ref<User>;
7+
8+
@prop({ required: true })
9+
valid: boolean;
10+
11+
@prop({ default: Date.now(), index: { expires: "15d" } })
12+
expireAt: number;
13+
}
14+
15+
const SessionModel = getModelForClass(Session, {
16+
schemaOptions: { timestamps: true },
17+
});
18+
19+
export default SessionModel;

0 commit comments

Comments
 (0)