Skip to content

Commit 9110e4e

Browse files
committed
complete api challenge
1 parent 5553b1d commit 9110e4e

13 files changed

Lines changed: 129 additions & 2 deletions

File tree

src/app.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@ import { notFound } from './middlewares/notFound.middleware.js';
1818
*/
1919
export function createApp() {
2020
// Your code here
21+
const app = express()
22+
23+
app.use(express.json())
24+
25+
app.get("/health", (_,res) => res.send({ok: true}))
26+
app.use("/api/auth", authRoutes)
27+
app.use("/api/users", userRoutes)
28+
app.use(notFound)
29+
app.use(errorHandler)
30+
31+
return app
2132
}

src/controllers/auth.controller.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ import { signToken } from '../utils/jwt.js';
1414
export async function register(req, res, next) {
1515
try {
1616
// Your code here
17+
18+
const {name, email, password} = req.body
19+
const isUserAlreadyExist = await User.findOne({email})
20+
21+
if(isUserAlreadyExist) {
22+
res.status(409).json({error: {message: "Email already exists"}})
23+
} else {
24+
const user = (await User.create({name, email, password})).toObject()
25+
delete user.password
26+
27+
res.status(201).json({user})
28+
}
1729
} catch (error) {
1830
next(error);
1931
}
@@ -33,6 +45,17 @@ export async function register(req, res, next) {
3345
export async function login(req, res, next) {
3446
try {
3547
// Your code here
48+
const {email, password} = req.body
49+
const user = await User.findOne({email}).select("+password")
50+
if(!user) return res.status(401).json({error: {message: "Invalid credentials"}})
51+
const isPasswordCorrect = await bcrypt.compare(password, user.password)
52+
if(!isPasswordCorrect) return res.status(401).json({error: {message: "Invalid credentials"}})
53+
54+
const token = signToken( { userId: user._id, email: user.email, role: user.role } )
55+
const userCopy = user.toObject()
56+
delete userCopy.password
57+
58+
return res.status(200).json({token, user: userCopy})
3659
} catch (error) {
3760
next(error);
3861
}
@@ -47,6 +70,7 @@ export async function login(req, res, next) {
4770
export async function me(req, res, next) {
4871
try {
4972
// Your code here
73+
return res.status(200).json({user: req.user})
5074
} catch (error) {
5175
next(error);
5276
}

src/controllers/user.controller.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { User } from '../models/user.model.js';
99
export async function listUsers(req, res, next) {
1010
try {
1111
// Your code here
12+
const users = await User.find()
13+
return res.status(200).json({users})
1214
} catch (error) {
1315
next(error);
1416
}
@@ -25,6 +27,12 @@ export async function listUsers(req, res, next) {
2527
export async function getUser(req, res, next) {
2628
try {
2729
// Your code here
30+
const userId = req.params?.id
31+
const user = await User.findById(userId)
32+
if(!user) {
33+
return res.status(404).json({error: {message: "User not found"}})
34+
}
35+
return res.status(200).json({user})
2836
} catch (error) {
2937
next(error);
3038
}
@@ -41,6 +49,13 @@ export async function getUser(req, res, next) {
4149
export async function deleteUser(req, res, next) {
4250
try {
4351
// Your code here
52+
const userId = req.params?.id
53+
const user = await User.findById(userId)
54+
if(!user) {
55+
return res.status(404).json({error: {message: "User not found"}})
56+
}
57+
await user.deleteOne()
58+
return res.status(200).json({message: "User deleted successfully"})
4459
} catch (error) {
4560
next(error);
4661
}

src/db/connect.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ import mongoose from 'mongoose';
99
*/
1010
export async function connectDB(uri) {
1111
// Your code here
12+
if(!uri) throw new Error("MongoDB URI is required")
13+
return await mongoose.connect(uri)
1214
}

src/middlewares/auth.middleware.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ import { verifyToken } from '../utils/jwt.js';
1818
export async function authenticate(req, res, next) {
1919
try {
2020
// Your code here
21+
if(!req.headers?.authorization || !req.headers?.authorization.startsWith("Bearer")) {
22+
return res.status(401).json({error: {message: "No token provided"}})
23+
}
24+
25+
const token = req.headers.authorization.split(" ")[1]
26+
try {
27+
const decoded = verifyToken(token)
28+
const user = await User.findById(decoded.userId)
29+
if(!user) return res.status(401).json({error: {message: "Invalid token"}})
30+
req.user = user
31+
} catch (error) {
32+
return res.status(401).json({error: {message: "Invalid token"}})
33+
} finally {
34+
next()
35+
}
2136
} catch (error) {
2237
return res.status(401).json({ error: { message: 'Invalid token' } });
2338
}

src/middlewares/error.middleware.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,11 @@
1010
*/
1111
export function errorHandler(error, req, res, next) {
1212
// Your code here
13+
if(error.name === 'ValidationError') {
14+
res.status(400).json({error: {message: error.message}})
15+
} else if(error.code === 11000) {
16+
res.status(409).json({error: {message: "Email already axists"}})
17+
} else {
18+
res.status(500).json({error: {message: error.message}})
19+
}
1320
}

src/middlewares/notFound.middleware.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
*/
66
export function notFound(req, res) {
77
// Your code here
8+
return res.status(404).json({error: {message: "Route not found"}})
89
}

src/middlewares/role.middleware.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,13 @@
1515
export function requireRole(...roles) {
1616
return (req, res, next) => {
1717
// Your code here
18+
const user = req?.user
19+
if(!user) {
20+
return res.status(401).json({error: {message: "Not authenticated"}})
21+
}
22+
if(!roles.some((role) => role === req.user?.role)) {
23+
return res.status(403).json({error: {message: "Forbidden"}})
24+
}
25+
next()
1826
};
1927
}

src/models/user.model.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,36 @@ import bcrypt from 'bcryptjs';
1818
const userSchema = new mongoose.Schema(
1919
{
2020
// Your schema fields here
21+
name: {
22+
type: String,
23+
required:true,
24+
trim:true,
25+
minLength:2,
26+
maxLength: 50
27+
},
28+
email: {
29+
type: String,
30+
required:true,
31+
unique:true,
32+
lowercase:true,
33+
trim:true,
34+
match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
35+
},
36+
password: {
37+
type: String,
38+
minLength:6,
39+
select:false,
40+
required:true
41+
},
42+
role: {
43+
type:String,
44+
enum: ["user", "admin"],
45+
default: "user"
46+
}
2147
},
2248
{
2349
// Schema options here
50+
timestamps:true
2451
}
2552
);
2653

@@ -41,4 +68,12 @@ const userSchema = new mongoose.Schema(
4168
* });
4269
*/
4370

71+
userSchema.pre("save", async function (next) {
72+
if(this.isModified("password")) {
73+
this.password = await bcrypt.hash(this.password, 10)
74+
}
75+
return next()
76+
})
77+
4478
// TODO: Create and export the User model
79+
export const User = mongoose.model("users", userSchema)

src/routes/auth.routes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ import { authenticate } from '../middlewares/auth.middleware.js';
1313
const router = Router();
1414

1515
// Your routes here
16+
router.post("/register", register)
17+
router.post("/login", login)
18+
router.get("/me", authenticate, me)
1619

1720
export default router;

0 commit comments

Comments
 (0)