Skip to content

Commit

Permalink
Merge pull request #11 from PolkadotEducation/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
arturgontijo authored Sep 1, 2024
2 parents 3392b6c + 799d60a commit fe9d194
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 44 deletions.
70 changes: 46 additions & 24 deletions src/controllers/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = 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);
if (newUser) {
await sendVerificationEmail(email, newUser.verifyToken!);
newUser.verifyToken = undefined;
Expand All @@ -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)}`);
Expand All @@ -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)}`);
}
Expand All @@ -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)}`);
}
Expand Down
4 changes: 2 additions & 2 deletions src/models/Lesson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Challenge {

@prop({
required: true,
type: [String],
type: Array<string>,
validate: {
validator: function (v: string[]) {
return v.length >= 3 && v.length <= 5;
Expand Down Expand Up @@ -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<Reference>, default: [] })
public references: Reference[];
}

Expand Down
20 changes: 16 additions & 4 deletions src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -38,7 +44,9 @@ class User extends BaseModel {
this: ReturnModelType<typeof User>,
email: string,
password: string,
name?: string,
name: string,
company: string,
isAdmin?: boolean,
): Promise<UserInfo | undefined> {
try {
const exists = await this.findOne({ email: email.toLowerCase() });
Expand All @@ -49,13 +57,17 @@ 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(),
});
return {
userId: user._id,
email: user.email,
name: user.name,
company: user.company,
isAdmin: user.isAdmin,
verifyToken: user.verifyToken,
lastActivity: user.lastActivity,
};
Expand Down Expand Up @@ -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(),
Expand All @@ -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();
Expand Down
11 changes: 6 additions & 5 deletions src/routes.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/types/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export type UserInfo = {
userId: string;
email: string;
name: string;
company: string;
isAdmin: boolean;
lastActivity: Date;
verifyToken?: string;
createdAt?: Date;
Expand Down
54 changes: 45 additions & 9 deletions tests/users.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,47 +28,83 @@ 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);
})
.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());
});
Expand Down

0 comments on commit fe9d194

Please sign in to comment.