Skip to content

Commit

Permalink
Merge branch 'main' into lakshya/F24/forgot-password-flow
Browse files Browse the repository at this point in the history
  • Loading branch information
laks0407 committed Nov 11, 2024
2 parents 76db079 + da373cc commit 1672300
Show file tree
Hide file tree
Showing 18 changed files with 310 additions and 70 deletions.
3 changes: 0 additions & 3 deletions backend/typescript/middlewares/validators/userValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ export const createUserDtoValidator = async (
if (!validatePrimitive(req.body.role, "string")) {
return res.status(400).send(getApiValidationError("role", "string"));
}
if (!validatePrimitive(req.body.password, "string")) {
return res.status(400).send(getApiValidationError("password", "string"));
}
if (
req.body.skillLevel !== undefined &&
req.body.skillLevel !== null &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DataType } from "sequelize-typescript";

import { Migration } from "../umzug";

const TABLE_NAME = "user_animal_types";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().createTable(TABLE_NAME, {
user_id: {
type: DataType.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: "users",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
animal_type_id: {
type: DataType.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: "animal_types",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
});
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().dropTable(TABLE_NAME);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { DataType } from "sequelize-typescript";

import { Migration } from "../umzug";

const TABLE_NAME = "behaviour_level_details";
const CONSTRAINT_NAME = "unique_behaviour_level";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().createTable(TABLE_NAME, {
id: {
type: DataType.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
behaviour_id: {
type: DataType.INTEGER,
allowNull: false,
references: {
model: "behaviours",
key: "id",
},
},
level: {
type: DataType.INTEGER,
validate: {
min: 1,
max: 4,
},
allowNull: false,
},
description: {
type: DataType.STRING,
allowNull: true,
},
training_instructions: {
type: DataType.STRING,
allowNull: true,
},
});

await sequelize.getQueryInterface().addConstraint(TABLE_NAME, {
fields: ["behaviour_id", "level"],
type: "unique",
name: CONSTRAINT_NAME,
});
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize
.getQueryInterface()
.removeConstraint(TABLE_NAME, CONSTRAINT_NAME);

await sequelize.getQueryInterface().dropTable(TABLE_NAME);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DataType } from "sequelize-typescript";
import { Migration } from "../umzug";

const TABLE_NAME = "behaviours";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize
.getQueryInterface()
.addColumn(TABLE_NAME, "parent_behaviour_id", {
type: DataType.INTEGER,
allowNull: true,
references: {
model: "behaviours",
key: "id",
},
});
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize
.getQueryInterface()
.removeColumn(TABLE_NAME, "parent_behaviour_id");
};
14 changes: 12 additions & 2 deletions backend/typescript/models/behaviour.model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Column, Model, Table } from "sequelize-typescript";
import {
Column,
DataType,
ForeignKey,
Model,
Table,
} from "sequelize-typescript";

@Table({ timestamps: false, tableName: "behaviours" })
export default class Behaviour extends Model {
@Column
@Column({ type: DataType.STRING, allowNull: false })
behaviour_name!: string;

@ForeignKey(() => Behaviour)
@Column({ type: DataType.INTEGER })
parent_behaviour_id?: number | null;
}
24 changes: 24 additions & 0 deletions backend/typescript/models/behaviourLevelDetails.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
Column,
Model,
Table,
ForeignKey,
DataType,
} from "sequelize-typescript";
import Behaviour from "./behaviour.model";

@Table({ timestamps: false, tableName: "behaviour_level_details" })
export default class BehaviourLevelDetails extends Model {
@ForeignKey(() => Behaviour)
@Column({ type: DataType.INTEGER, allowNull: false })
behaviour_id!: number;

@Column({ type: DataType.INTEGER, allowNull: false })
level!: number;

@Column({ type: DataType.STRING, allowNull: true })
description?: string;

@Column({ type: DataType.STRING, allowNull: true })
training_instructions?: string;
}
12 changes: 11 additions & 1 deletion backend/typescript/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import * as path from "path";
import { Sequelize } from "sequelize-typescript";
import User from "./user.model";
import AnimalType from "./animalType.model";
import UserAnimalType from "./userAnimalType.model";
import defineRelationships from "./modelRelationships";

const DATABASE_URL =
process.env.NODE_ENV === "production"
Expand All @@ -8,6 +12,12 @@ const DATABASE_URL =
: `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_DEV}`;

/* eslint-disable-next-line import/prefer-default-export */
export const sequelize = new Sequelize(DATABASE_URL, {
const sequelize = new Sequelize(DATABASE_URL, {
models: [path.join(__dirname, "/*.model.ts")],
});

sequelize.addModels([User, AnimalType, UserAnimalType]);

defineRelationships();

export { sequelize, User, AnimalType, UserAnimalType };
7 changes: 7 additions & 0 deletions backend/typescript/models/modelRelationships.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import User from "./user.model";
import AnimalType from "./animalType.model";
import UserAnimalType from "./userAnimalType.model";

export default function defineRelationships(): void {
User.belongsToMany(AnimalType, { through: UserAnimalType });
}
26 changes: 26 additions & 0 deletions backend/typescript/models/userAnimalType.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
Column,
DataType,
Model,
Table,
ForeignKey,
PrimaryKey,
} from "sequelize-typescript";
import User from "./user.model";
import AnimalType from "./animalType.model";

@Table({
tableName: "User_AnimalTypes",
timestamps: true,
})
export default class UserAnimalType extends Model {
@ForeignKey(() => User)
@PrimaryKey
@Column({ type: DataType.INTEGER, allowNull: false })
user_id!: number;

@ForeignKey(() => AnimalType)
@PrimaryKey
@Column({ type: DataType.INTEGER, allowNull: false })
animal_type_id!: number;
}
4 changes: 1 addition & 3 deletions backend/typescript/rest/authRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import IAuthService from "../services/interfaces/authService";
import IEmailService from "../services/interfaces/emailService";
import IUserService from "../services/interfaces/userService";
import { getErrorMessage } from "../utilities/errorUtils";
import { Role, UserStatus } from "../types";
import { Role } from "../types";

const authRouter: Router = Router();
const userService: IUserService = new UserService();
Expand Down Expand Up @@ -53,12 +53,10 @@ authRouter.post("/register", registerRequestValidator, async (req, res) => {
lastName: req.body.lastName,
email: req.body.email,
role: req.body.role ?? Role.VOLUNTEER,
status: req.body.status ?? UserStatus.ACTIVE, // TODO: make this default to inactive once user registration flow is done
skillLevel: req.body.skillLevel ?? null,
canSeeAllLogs: req.body.canSeeAllLogs ?? null,
canAssignUsersToTasks: req.body.canAssignUsersToTasks ?? null,
phoneNumber: req.body.phoneNumber ?? null,
password: req.body.password,
});

const authDTO = await authService.generateToken(
Expand Down
9 changes: 4 additions & 5 deletions backend/typescript/rest/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import UserService from "../services/implementations/userService";
import IAuthService from "../services/interfaces/authService";
import IEmailService from "../services/interfaces/emailService";
import IUserService from "../services/interfaces/userService";
import { Role, UserDTO, UserStatus } from "../types";
import { Role, UserDTO } from "../types";
import {
getErrorMessage,
NotFoundError,
Expand Down Expand Up @@ -99,23 +99,22 @@ userRouter.get("/", async (req, res) => {
}
});

// This endpoint is for testing purposes
/* Create a user */
userRouter.post("/", createUserDtoValidator, async (req, res) => {
try {
const newUser = await userService.createUser({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
role: req.body.role ?? Role.VOLUNTEER,
status: UserStatus.INVITED,
role: req.body.role,
skillLevel: req.body.skillLevel ?? null,
canSeeAllLogs: req.body.canSeeAllLogs ?? null,
canAssignUsersToTasks: req.body.canSeeAllUsers ?? null,
phoneNumber: req.body.phoneNumber ?? null,
password: req.body.password,
});

await authService.sendEmailVerificationLink(req.body.email);
// await authService.sendEmailVerificationLink(req.body.email);

res.status(201).json(newUser);
} catch (error: unknown) {
Expand Down
30 changes: 11 additions & 19 deletions backend/typescript/services/implementations/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as firebaseAdmin from "firebase-admin";
import IAuthService from "../interfaces/authService";
import IEmailService from "../interfaces/emailService";
import IUserService from "../interfaces/userService";
import { AuthDTO, Role, Token, UserStatus } from "../../types";
import { AuthDTO, Role, Token } from "../../types";
import { getErrorMessage } from "../../utilities/errorUtils";
import FirebaseRestClient from "../../utilities/firebaseRestClient";
import logger from "../../utilities/logger";
Expand Down Expand Up @@ -58,18 +58,12 @@ class AuthService implements IAuthService {
/* eslint-disable-next-line no-empty */
} catch (error) {}

const user = await this.userService.createUser(
{
firstName: googleUser.firstName,
lastName: googleUser.lastName,
email: googleUser.email,
role: Role.STAFF,
status: UserStatus.ACTIVE,
password: "",
},
googleUser.localId,
"GOOGLE",
);
const user = await this.userService.createUser({
firstName: googleUser.firstName,
lastName: googleUser.lastName,
email: googleUser.email,
role: Role.STAFF,
});

return { ...token, ...user };
} catch (error) {
Expand Down Expand Up @@ -175,12 +169,10 @@ class AuthService implements IAuthService {
const userRole = await this.userService.getUserRoleByAuthId(
decodedIdToken.uid,
);

const firebaseUser = await firebaseAdmin
.auth()
.getUser(decodedIdToken.uid);

return firebaseUser.emailVerified && roles.has(userRole);
// const firebaseUser = await firebaseAdmin
// .auth()
// .getUser(decodedIdToken.uid);
return /* firebaseUser.emailVerified && */ roles.has(userRole);
} catch (error) {
return false;
}
Expand Down
29 changes: 12 additions & 17 deletions backend/typescript/services/implementations/userService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as firebaseAdmin from "firebase-admin";
import IUserService from "../interfaces/userService";
import { CreateUserDTO, Role, UpdateUserDTO, UserDTO } from "../../types";
import {
CreateUserDTO,
Role,
UpdateUserDTO,
UserDTO,
UserStatus,
} from "../../types";
import { getErrorMessage, NotFoundError } from "../../utilities/errorUtils";
import logger from "../../utilities/logger";
import PgUser from "../../models/user.model";
Expand Down Expand Up @@ -165,33 +171,22 @@ class UserService implements IUserService {
return userDtos;
}

async createUser(
user: CreateUserDTO,
authId?: string,
signUpMethod = "PASSWORD",
): Promise<UserDTO> {
async createUser(user: CreateUserDTO): Promise<UserDTO> {
let newUser: PgUser;
let firebaseUser: firebaseAdmin.auth.UserRecord;

try {
if (signUpMethod === "GOOGLE") {
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
firebaseUser = await firebaseAdmin.auth().getUser(authId!);
} else {
// signUpMethod === PASSWORD
firebaseUser = await firebaseAdmin.auth().createUser({
email: user.email,
password: user.password,
});
}
firebaseUser = await firebaseAdmin.auth().createUser({
email: user.email,
});

try {
newUser = await PgUser.create({
first_name: user.firstName,
last_name: user.lastName,
auth_id: firebaseUser.uid,
role: user.role,
status: user.status,
status: UserStatus.INVITED,
email: firebaseUser.email ?? "",
skill_level: user.skillLevel,
can_see_all_logs: user.canSeeAllLogs,
Expand Down
Loading

0 comments on commit 1672300

Please sign in to comment.