Skip to content

Commit d42aab5

Browse files
author
DrakeBackend
committed
chore/feat: user, auth, role base auth, global routes
1 parent 470141b commit d42aab5

24 files changed

+522
-182
lines changed

README.md

+25-7
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,33 @@
99
- TypeScript Support and Config
1010
- Basic CLI for crating new module
1111

12-
### How to use CLI
13-
1412
```bash
1513
npm install tsx --save-dev
1614
or
1715
npm install -g tsx
1816
npm install -g bun
1917
bun install
18+
19+
# Add any package
20+
bun add packageName
21+
22+
# Add Dev Package
23+
bun add --save-dev packageName
2024
```
2125

26+
### Start For Development
27+
28+
```bash
29+
bun start dev
30+
```
31+
32+
### How to use CLI
33+
2234
```bash
2335

2436
npm run cli
2537

26-
or
38+
or
2739

2840
make cli
2941
```
@@ -32,15 +44,21 @@ make cli
3244
- Based on that will create controller, route and validation files inside API folder
3345

3446
### Recommended Folder Structure:
47+
3548
<!-- Feature-based Structure design pattern -->
36-
______
49+
50+
---
51+
3752
< Hi! >
38-
------
53+
54+
---
55+
3956
\ ^__^
4057
\ (oo)\_______
4158
(__)\ )\/\
4259
||----w |
4360
|| ||
61+
4462
```bash
4563

4664
project-root/
@@ -112,6 +130,6 @@ project-root/
112130

113131
```
114132

115-
### Auth Module Working
133+
### Auth Module Working
116134

117-
- Auth With JWT
135+
- Auth With JWT

bun.lockb

27.3 KB
Binary file not shown.

package.json

+7
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@
1919
"cli": "tsx ./script/index.ts"
2020
},
2121
"dependencies": {
22+
"@types/bcrypt": "^5.0.2",
23+
"@types/jsonwebtoken": "^9.0.7",
24+
"bcrypt": "^5.1.1",
2225
"compression": "^1.7.4",
2326
"cors": "^2.8.5",
2427
"dotenv": "^16.4.5",
2528
"express": "^5.0.1",
2629
"helmet": "^8.0.0",
30+
"http-status-codes": "^2.3.0",
31+
"joi": "^17.13.3",
32+
"jsonwebtoken": "^9.0.2",
2733
"morgan": "^1.10.0",
2834
"supertest": "^7.0.0"
2935
},
@@ -47,6 +53,7 @@
4753
"globals": "^15.11.0",
4854
"husky": "^9.1.6",
4955
"lint-staged": "^15.2.10",
56+
"mongoose": "^8.9.5",
5057
"prettier": "^3.3.3",
5158
"rimraf": "^6.0.1",
5259
"tsx": "^4.19.1",

public/favicon.ico

-15 KB
Binary file not shown.

src/api/auth/auth.controller.ts

+39-31
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,52 @@
1-
import { Request, Response } from 'express';
2-
import { AuthService } from '../services/auth.service';
3-
import { validationResult } from 'express-validator';
1+
import { NextFunction, Request, Response } from "express";
2+
import { getReasonPhrase, StatusCodes } from "http-status-codes";
3+
import authService from "./auth.service";
44

55
class AuthController {
6-
static async register(req: Request, res: Response) {
6+
public async register(
7+
req: Request,
8+
res: Response,
9+
next: NextFunction,
10+
): Promise<void> {
711
try {
8-
// Validate input data
9-
const errors = validationResult(req);
10-
if (!errors.isEmpty()) {
11-
return res.status(400).json({ errors: errors.array() });
12+
const user = await authService.register(req.body);
13+
res.status(StatusCodes.CREATED).json({
14+
data: user,
15+
message: getReasonPhrase(StatusCodes.CREATED),
16+
});
17+
} catch (err: unknown) {
18+
if (err instanceof Error && err.message === "Email already in use") {
19+
res.status(StatusCodes.CONFLICT).json({
20+
message: err.message,
21+
});
22+
return;
1223
}
13-
14-
// Register user
15-
const { username, email, password } = req.body;
16-
const user = await AuthService.register(username, email, password);
17-
return res.status(201).json({ message: 'User created successfully', user });
18-
} catch (error) {
19-
return res.status(500).json({ error: error.message });
24+
next(err);
2025
}
2126
}
2227

23-
static async login(req: Request, res: Response) {
28+
public async login(
29+
req: Request,
30+
res: Response,
31+
next: NextFunction,
32+
): Promise<void> {
2433
try {
2534
const { email, password } = req.body;
26-
const token = await AuthService.login(email, password);
27-
return res.status(200).json({ message: 'Login successful', token });
28-
} catch (error) {
29-
return res.status(500).json({ error: error.message });
30-
}
31-
}
32-
33-
static async authenticate(req: Request, res: Response) {
34-
try {
35-
const token = req.headers['authorization']?.split(' ')[1];
36-
const decoded = await AuthService.authenticate(token);
37-
return res.status(200).json({ message: 'Authentication successful', user: decoded });
38-
} catch (error) {
39-
return res.status(401).json({ error: 'Unauthorized' });
35+
const token = await authService.login(email, password);
36+
res.status(StatusCodes.OK).json({
37+
token,
38+
message: getReasonPhrase(StatusCodes.OK),
39+
});
40+
} catch (err: unknown) {
41+
if (err instanceof Error && err.message === "Invalid email or password") {
42+
res.status(StatusCodes.UNAUTHORIZED).json({
43+
message: err.message,
44+
});
45+
return;
46+
}
47+
next(err);
4048
}
4149
}
4250
}
4351

44-
export { AuthController };
52+
export default new AuthController();

src/api/auth/auth.route.ts

+6-20
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
1-
import express from 'express';
2-
import { AuthController } from './auth.controller';
3-
import { check } from 'express-validator';
4-
import { AuthMiddleware } from '../middlewares/auth.middleware';
1+
import express from "express";
2+
import authController from "./auth.controller";
53

6-
const router = express.Router();
4+
const authRouter = express.Router();
75

8-
// Registration route
9-
router.post('/register', [
10-
check('email').isEmail().withMessage('Invalid email format'),
11-
check('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
12-
], AuthController.register);
6+
authRouter.post("/register", authController.register);
7+
authRouter.post("/login", authController.login);
138

14-
// Login route
15-
router.post('/login', [
16-
check('email').isEmail().withMessage('Invalid email format'),
17-
check('password').notEmpty().withMessage('Password is required')
18-
], AuthController.login);
19-
20-
// Authentication middleware example
21-
router.get('/authenticate', AuthMiddleware.authenticate, AuthController.authenticate);
22-
23-
export { router };
9+
export default authRouter;

src/api/auth/auth.service.ts

+59-36
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,74 @@
1-
import bcrypt from 'bcryptjs';
2-
import jwt from 'jsonwebtoken';
3-
import { UserModel } from '../models/user.model';
1+
import bcrypt from "bcrypt";
2+
import { signToken } from "../../utils/jwt.util";
3+
import UserModel from "../user/user.model";
4+
import { IUser, UserRole } from "../user/user.types";
45

56
class AuthService {
6-
static async register(username: string, email: string, password: string) {
7-
// Check if user exists
8-
const existingUser = await UserModel.findOne({ email });
9-
if (existingUser) {
10-
throw new Error('User already exists');
7+
public async register(userData: IUser): Promise<IUser> {
8+
try {
9+
const hashedPassword = await bcrypt.hash(userData.password, 10);
10+
const user = await UserModel.create({
11+
...userData,
12+
password: hashedPassword,
13+
});
14+
return user;
15+
} catch (err) {
16+
if (
17+
err instanceof Error &&
18+
"code" in err &&
19+
(err as { code: number }).code === 11000
20+
) {
21+
throw new Error("Duplicate key error: Email already exists");
22+
}
23+
if (err instanceof Error) {
24+
throw new Error(`Error registering user: ${err.message}`);
25+
}
26+
throw new Error("Unexpected error occurred during registration");
1127
}
28+
}
1229

13-
// Hash password
14-
const hashedPassword = await bcrypt.hash(password, 10);
30+
public async login(email: string, password: string): Promise<string> {
31+
try {
32+
const user = await UserModel.findOne({ email });
33+
if (!user) {
34+
throw new Error("Invalid email or password");
35+
}
1536

16-
// Create and save user
17-
const user = new UserModel({ username, email, password: hashedPassword });
18-
await user.save();
37+
const isPasswordValid = await bcrypt.compare(password, user.password);
38+
if (!isPasswordValid) {
39+
throw new Error("Invalid email or password");
40+
}
1941

20-
return user;
21-
}
42+
const token = signToken({
43+
id: user.id,
44+
role: user.role,
45+
email: user.email,
46+
username: user.username,
47+
});
2248

23-
static async login(email: string, password: string) {
24-
const user = await UserModel.findOne({ email });
25-
if (!user) {
26-
throw new Error('Invalid credentials');
49+
return token;
50+
} catch (err: unknown) {
51+
if (err instanceof Error) {
52+
throw new Error(`Error during login: ${err.message}`);
53+
}
54+
throw new Error("Unexpected error occurred during login");
2755
}
28-
29-
// Check password
30-
const isMatch = await bcrypt.compare(password, user.password);
31-
if (!isMatch) {
32-
throw new Error('Invalid credentials');
33-
}
34-
35-
// Generate JWT token
36-
const token = jwt.sign({ id: user._id, email: user.email }, process.env.JWT_SECRET as string, { expiresIn: '1h' });
37-
return token;
3856
}
3957

40-
static async authenticate(token: string) {
58+
public async checkRole(userId: string, role: UserRole): Promise<boolean> {
4159
try {
42-
// Verify and decode the JWT token
43-
const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
44-
return decoded;
45-
} catch (error) {
46-
throw new Error('Invalid or expired token');
60+
const user = await UserModel.findById(userId);
61+
if (!user) {
62+
throw new Error("User not found");
63+
}
64+
return user.role === role;
65+
} catch (err: unknown) {
66+
if (err instanceof Error) {
67+
throw new Error(`Error checking role: ${err.message}`);
68+
}
69+
throw new Error("Unexpected error occurred while checking role");
4770
}
4871
}
4972
}
5073

51-
export { AuthService };
74+
export default new AuthService();

0 commit comments

Comments
 (0)