From a940fa999e42b382331f3a5b77a6c011eba61452 Mon Sep 17 00:00:00 2001 From: Lauro Gripa Date: Mon, 26 Aug 2024 20:47:13 -0300 Subject: [PATCH 1/3] Change type definition syntax (#8) --- src/models/Lesson.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/Lesson.ts b/src/models/Lesson.ts index 162677d..50db993 100644 --- a/src/models/Lesson.ts +++ b/src/models/Lesson.ts @@ -8,7 +8,7 @@ class Challenge { @prop({ required: true, - type: [String], + type: Array, validate: { validator: function (v: string[]) { return v.length >= 3 && v.length <= 5; @@ -43,7 +43,7 @@ class Lesson extends BaseModel { @prop({ required: true, type: () => Challenge, default: {} }) public challenge: Challenge; - @prop({ required: false, type: () => [Reference], default: [] }) + @prop({ required: false, type: () => Array, default: [] }) public references: Reference[]; } From 537168212d3cb349e80388663b2d32f50682b9fe Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 29 Aug 2024 07:56:50 -0300 Subject: [PATCH 2/3] Add update user logic + isAdmin & company fields --- src/controllers/users.ts | 70 ++++++++++++++++++++++++++-------------- src/models/User.ts | 20 +++++++++--- src/routes.ts | 11 ++++--- src/types/User.ts | 2 ++ tests/users.test.ts | 54 +++++++++++++++++++++++++------ 5 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/controllers/users.ts b/src/controllers/users.ts index 50fea13..43792d7 100644 --- a/src/controllers/users.ts +++ b/src/controllers/users.ts @@ -4,12 +4,10 @@ import { sendVerificationEmail } from "@/helpers/aws/ses"; export const createUser = async (req: Request, res: Response) => { try { - const { email, password, name } = req.body; - if (!email || !password) { - return res.status(400).send({ error: { message: "Missing email or password" } }); - } + const { email, password, name, company, isAdmin } = req.body; + if (!email || !password) return res.status(400).send({ error: { message: "Missing email or password" } }); - const newUser = await UserModel.createUser(email, password, name); + const newUser = await UserModel.createUser(email, password, name, company, isAdmin); if (newUser) { await sendVerificationEmail(email, newUser.verifyToken!); newUser.verifyToken = undefined; @@ -28,11 +26,10 @@ export const createUser = async (req: Request, res: Response) => { export const getUser = async (req: Request, res: Response) => { try { - const { userId } = req.query; - if (!userId) { - return res.status(400).send({ error: { message: "Missing userId" } }); - } - const user = await UserModel.findOne({ _id: userId }, { email: 1, name: 1 }); + const { id } = req.params; + if (!id) return res.status(400).send({ error: { message: "Missing userId" } }); + + const user = await UserModel.findOne({ _id: id }, { email: 1, name: 1, company: 1, isAdmin: 1 }); if (user) return res.status(200).send(user); } catch (e) { console.log(`[ERROR][getUser] ${JSON.stringify(e)}`); @@ -45,17 +42,45 @@ export const getUser = async (req: Request, res: Response) => { }); }; +export const updateUser = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { email, name, company, isAdmin, password } = req.body; + + const user = await UserModel.findById(id); + if (!user) return res.status(404).send({ error: { message: "User not found" } }); + + if (email) user.email = email; + if (name) user.name = name; + if (company) user.company = company; + if (typeof isAdmin === "boolean") user.isAdmin = isAdmin; + if (password) user.password = await UserModel.hashPassword(password); + await user.save(); + + return res.status(200).send({ + email: user.email, + name: user.name, + company: user.company, + isAdmin: user.isAdmin, + }); + } catch (e) { + console.log(`[ERROR][updateUser] ${JSON.stringify(e)}`); + } + + return res.status(400).send({ + error: { + message: "User not updated", + }, + }); +}; + export const deleteUser = async (req: Request, res: Response) => { try { - const { email } = req.body; - if (!email) { - return res.status(400).send({ error: { message: "Missing email" } }); - } + const { id } = req.params; + if (!id) return res.status(400).send({ error: { message: "Missing userId" } }); - const result = await UserModel.deleteOne({ email }); - if (result?.deletedCount > 0) { - return res.status(200).send({ message: `User '${email}' deleted` }); - } + const result = await UserModel.deleteOne({ _id: id }); + if (result?.deletedCount > 0) return res.status(200).send({ message: `User '${id}' deleted` }); } catch (e) { console.log(`[ERROR][deleteUser] ${JSON.stringify(e)}`); } @@ -70,13 +95,10 @@ export const deleteUser = async (req: Request, res: Response) => { export const loginUser = async (req: Request, res: Response) => { try { const { email, password } = req.body; - if (!email || !password) { - return res.status(400).send({ error: { message: "Missing email or password" } }); - } + if (!email || !password) return res.status(400).send({ error: { message: "Missing email or password" } }); + const authToken = await UserModel.login(email, password, true); - if (authToken) { - return res.status(200).send({ jwt: authToken }); - } + if (authToken) return res.status(200).send({ jwt: authToken }); } catch (e) { console.log(`[ERROR][loginUser] ${JSON.stringify(e)}`); } diff --git a/src/models/User.ts b/src/models/User.ts index de3579d..a96439c 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -18,6 +18,12 @@ class User extends BaseModel { @prop({ required: true, type: String }) public name: string; + @prop({ required: true, type: String, default: "" }) + public company: string; + + @prop({ required: true, type: Boolean, default: false }) + public isAdmin: boolean; + @prop({ required: false, type: String }) public verifyToken?: string; @@ -38,7 +44,9 @@ class User extends BaseModel { this: ReturnModelType, email: string, password: string, - name?: string, + name: string, + company: string, + isAdmin: boolean, ): Promise { try { const exists = await this.findOne({ email: email.toLowerCase() }); @@ -49,6 +57,8 @@ class User extends BaseModel { email: email.toLowerCase(), password: await this.hashPassword(password), name, + company: company || "", + isAdmin: isAdmin || false, verifyToken: randomBytes(16).toString("hex"), lastActivity: new Date(), }); @@ -56,6 +66,8 @@ class User extends BaseModel { userId: user._id, email: user.email, name: user.name, + company: user.company, + isAdmin: user.isAdmin, verifyToken: user.verifyToken, lastActivity: user.lastActivity, }; @@ -83,6 +95,8 @@ class User extends BaseModel { id: this._id, email: this.email, name: this.name, + company: this.company, + isAdmin: this.isAdmin, }, createdAt: moment().unix(), expiresAt: moment().add(expiresDays, "days").unix(), @@ -98,9 +112,7 @@ class User extends BaseModel { } const validPassword = await user.comparePassword(password); - if (!validPassword) { - throw "Invalid Password"; - } + if (!validPassword) throw "Invalid Password"; user.lastActivity = new Date(); await user.save(); diff --git a/src/routes.ts b/src/routes.ts index 2c8d8e6..6b0c110 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,7 +1,7 @@ import { Express, Request, Response } from "express"; // Controllers -import { createUser, deleteUser, getUser, loginUser } from "@/controllers/users"; +import { createUser, deleteUser, getUser, loginUser, updateUser } from "@/controllers/users"; import { createLesson, deleteLesson, getLesson, updateLesson } from "@/controllers/lessons"; import authMiddleware from "./middlewares/auth"; @@ -12,10 +12,11 @@ const router = (app: Express) => { app.get("/status", (_req: Request, res: Response) => res.status(200).json({ type: "success" })); // Users - app.post("/user", createUser); - app.get("/user", getUser); - app.post("/user/login", loginUser); - app.delete("/user", [corsConfig(), authMiddleware], deleteUser); + app.post("/users", createUser); + app.get("/users/:id", getUser); + app.put("/users/:id", [corsConfig(), authMiddleware], updateUser); + app.delete("/users/:id", [corsConfig(), authMiddleware], deleteUser); + app.post("/users/login", loginUser); // Tracks // Modules diff --git a/src/types/User.ts b/src/types/User.ts index 88d2030..2cdfb8b 100644 --- a/src/types/User.ts +++ b/src/types/User.ts @@ -2,6 +2,8 @@ export type UserInfo = { userId: string; email: string; name: string; + company: string; + isAdmin: boolean; lastActivity: Date; verifyToken?: string; createdAt?: Date; diff --git a/tests/users.test.ts b/tests/users.test.ts index 557a4ec..b32c29e 100644 --- a/tests/users.test.ts +++ b/tests/users.test.ts @@ -28,16 +28,20 @@ describe("Setting API Server up...", () => { }); describe("Users", () => { - it("Create a User (POST /user)", async () => { + it("Create a User (POST /users)", async () => { await mongoDBsetup(MONGODB_DATABASE_NAME); const email = "user1@polkadot.education"; const name = "User One"; const password = "superSecret"; + const company = "company"; + const isAdmin = false; await axios - .post(`${API_URL}/user`, { + .post(`${API_URL}/users`, { email, name, password, + company, + isAdmin, }) .then((r) => { expect(r.data.email).toEqual(email); @@ -45,30 +49,62 @@ describe("Setting API Server up...", () => { .catch((e) => expect(e).toBeUndefined()); }); - it("Get a User (GET /user)", async () => { + it("Get a User (GET /users)", async () => { await mongoDBsetup(MONGODB_DATABASE_NAME); const email = "user2@polkadot.education"; const name = "User Two"; const password = "superSecret"; - const user = await UserModel.createUser(email, password, name); + const user = await UserModel.createUser(email, password, name, "company", false); await axios - .get(`${API_URL}/user?userId=${user?.userId}`) + .get(`${API_URL}/users/${user?.userId}`) .then((r) => { expect(r.data.email).toEqual(email); }) .catch((e) => expect(e).toBeUndefined()); }); - it("Delete a User (DELETE /user)", async () => { + it("Update a User (PUT /users)", async () => { await mongoDBsetup(MONGODB_DATABASE_NAME); const email = "user3@polkadot.education"; const name = "User Three"; const password = "superSecret"; - await UserModel.createUser(email, password, name); + const user = await UserModel.createUser(email, password, name, "company", false); + + const newEmail = "New Email"; + const newPassword = "newSuperSecret"; + const newName = "New Name"; + const newCompany = "New Company"; + await axios + .put(`${API_URL}/users/${user?.userId}`, { + email: newEmail, + password: newPassword, + name: newName, + company: newCompany, + isAdmin: true, + }) + .then(async (r) => { + expect(r.data.email).toEqual(newEmail); + expect(r.data.name).toEqual(newName); + expect(r.data.company).toEqual(newCompany); + expect(r.data.isAdmin).toEqual(true); + // Password check + const updatedUser = await UserModel.findById(user?.userId); + const validPassword = await updatedUser?.comparePassword(newPassword); + expect(validPassword).toBeTruthy(); + }) + .catch((e) => expect(e).toBeUndefined()); + }); + + it("Delete a User (DELETE /users)", async () => { + await mongoDBsetup(MONGODB_DATABASE_NAME); + const email = "user4@polkadot.education"; + const name = "User Four"; + const password = "superSecret"; + const user = await UserModel.createUser(email, password, name, "company", false); await axios - .delete(`${API_URL}/user`, { data: { email } }) + .delete(`${API_URL}/users/${user?.userId}`) .then((r) => { - expect(r.data.message).toEqual(`User '${email}' deleted`); + expect(r.data.message).toEqual(`User '${user?.userId}' deleted`); }) .catch((e) => expect(e).toBeUndefined()); }); From da85c97e899f37796d4f20d6ed989635c6e10c6a Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 29 Aug 2024 09:07:17 -0300 Subject: [PATCH 3/3] Remove isAdmin from createUser() (POST /users) --- src/controllers/users.ts | 4 ++-- src/models/User.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/users.ts b/src/controllers/users.ts index 43792d7..468c073 100644 --- a/src/controllers/users.ts +++ b/src/controllers/users.ts @@ -4,10 +4,10 @@ import { sendVerificationEmail } from "@/helpers/aws/ses"; export const createUser = async (req: Request, res: Response) => { try { - const { email, password, name, company, isAdmin } = req.body; + const { email, password, name, company } = req.body; if (!email || !password) return res.status(400).send({ error: { message: "Missing email or password" } }); - const newUser = await UserModel.createUser(email, password, name, company, isAdmin); + const newUser = await UserModel.createUser(email, password, name, company); if (newUser) { await sendVerificationEmail(email, newUser.verifyToken!); newUser.verifyToken = undefined; diff --git a/src/models/User.ts b/src/models/User.ts index a96439c..da10ed1 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -46,7 +46,7 @@ class User extends BaseModel { password: string, name: string, company: string, - isAdmin: boolean, + isAdmin?: boolean, ): Promise { try { const exists = await this.findOne({ email: email.toLowerCase() });