Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions backend/typescript/rest/petRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import {
NotFoundError,
} from "../utilities/errorUtils";
import { sendResponseByMimeType } from "../utilities/responseUtil";
import IUserService from "../services/interfaces/userService";
import UserService from "../services/implementations/userService";
import { UserDTO } from "../types";

const petRouter: Router = Router();
const petService: IPetService = new PetService();
const userService: IUserService = new UserService();

/* Update Pet by id */
petRouter.put("/:id", petRequestDtoValidators, async (req, res) => {
Expand Down Expand Up @@ -140,4 +144,29 @@ petRouter.get("/:id", async (req, res) => {
}
});

petRouter.get("/match/pets/:userId", async (req, res) => {
const { userId } = req.params;

if (userId) {
if (typeof userId !== "string") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a middleware to handle the param validation will make this code a lot neater

See /middlewares/validators for examples of how other endpoints used it
-> create a matchPetsValidator function in petValidators

Do a similar process for /match/users/:petId

res
.status(400)
.json({ error: "userId query parameter must be a string." });
} else if (Number.isNaN(Number(userId))) {
res.status(400).json({ error: "Invalid user ID" });
} else {
try {
const user: UserDTO = await userService.getUserById(userId);
await petService.getMatchingPetsForUser(user.colorLevel);
} catch (error: unknown) {
if (error instanceof NotFoundError) {
res.status(400).json({ error: getErrorMessage(error) });
} else {
res.status(500).json({ error: getErrorMessage(error) });
}
}
}
}
});

export default petRouter;
28 changes: 28 additions & 0 deletions backend/typescript/rest/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ import {
INTERNAL_SERVER_ERROR_MESSAGE,
} from "../utilities/errorUtils";
import { sendResponseByMimeType } from "../utilities/responseUtil";
import { IPetService, PetResponseDTO } from "../services/interfaces/petService";
import PetService from "../services/implementations/petService";

const userRouter: Router = Router();

const userService: IUserService = new UserService();
const emailService: IEmailService = new EmailService(nodemailerConfig);
const authService: IAuthService = new AuthService(userService, emailService);
const petService: IPetService = new PetService();

/* Get all users, optionally filter by a userId or email query parameter to retrieve a single user */
userRouter.get("/", async (req, res) => {
Expand Down Expand Up @@ -325,4 +328,29 @@ userRouter.delete("/", async (req, res) => {
.json({ error: "Must supply one of userId or email as query parameter." });
});

userRouter.get("/match/users/:petId", async (req, res) => {
const { petId } = req.params;

if (petId) {
if (typeof petId !== "string") {
res
.status(400)
.json({ error: "petId query parameter must be a string." });
} else if (Number.isNaN(Number(petId))) {
res.status(400).json({ error: "Invalid pet ID" });
} else {
try {
const pet: PetResponseDTO = await petService.getPet(petId);
await userService.getMatchingUsersForPet(pet.colorLevel);
} catch (error: unknown) {
if (error instanceof NotFoundError) {
res.status(400).json({ error: getErrorMessage(error) });
} else {
res.status(500).json({ error: getErrorMessage(error) });
}
}
}
}
});

export default userRouter;
46 changes: 43 additions & 3 deletions backend/typescript/services/implementations/petService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Transaction } from "sequelize";
import { Transaction, Op } from "sequelize";
import PgPet from "../../models/pet.model";
// import PgTask from "../../models/task.model";
// import PgTaskTemplate from "../../models/taskTemplate.model";
Expand All @@ -14,13 +14,13 @@ import {
import { getErrorMessage, NotFoundError } from "../../utilities/errorUtils";
import logger from "../../utilities/logger";
import { sequelize } from "../../models";
// import TaskTemplate from "../../models/taskTemplate.model";
// import { Role } from "../../types";
// import ActivityType from "../../models/activityType.model";

const Logger = logger(__filename);

class PetService implements IPetService {
/* eslint-disable class-methods-use-this */

getAgeFromBirthday(birthday: string): number {
const parsedBirthday = Date.parse(birthday);
const currentDate = new Date();
Expand Down Expand Up @@ -314,6 +314,46 @@ class PetService implements IPetService {
}
return id;
}

async getMatchingPetsForUser(
userColorLevel: number,
): Promise<PetResponseDTO[]> {
try {
const matchingPets = await PgPet.findAll({
where: {
color_level: {
[Op.lte]: userColorLevel,
},
},
});

return matchingPets.map((pet) => ({
id: pet.id,
name: pet.name,
animalTag: pet.animal_tag,
colorLevel: pet.color_level,
status: pet.status,
breed: pet.breed,
age: pet.birthday ? this.getAgeFromBirthday(pet.birthday) : undefined,
weight: pet.weight,
sex: pet.sex,
photo: pet.photo,
careInfo: {
id: pet.petCareInfo?.id,
safetyInfo: pet.petCareInfo?.safety_info,
medicalInfo: pet.petCareInfo?.medical_info,
managementInfo: pet.petCareInfo?.management_info,
},
}));
} catch (error) {
Logger.error(
`Failed to get matching pets for user. Reason = ${getErrorMessage(
error,
)}`,
);
throw error;
}
}
}

export default PetService;
17 changes: 17 additions & 0 deletions backend/typescript/services/implementations/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,23 @@ class UserService implements IUserService {
throw error;
}
}

async getMatchingUsersForPet(petColorLevel: number): Promise<UserDTO[]> {
try {
const allUsers = await this.getUsers();

return allUsers.filter(
(user: UserDTO) => user.colorLevel >= petColorLevel,
);
} catch (error) {
Logger.error(
`Failed to get matching users for pet. Reason = ${getErrorMessage(
error,
)}`,
);
throw error;
}
}
}

export default UserService;
8 changes: 8 additions & 0 deletions backend/typescript/services/interfaces/matchmakingService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PetResponseDTO } from "./petService";
import { UserDTO } from "../../types";

export interface IMatchmakingService {
/* eslint-disable class-methods-use-this */
getMatchingPetsForUser(userId: string): Promise<PetResponseDTO[]>;
getMatchingUsersForPet(petId: string): Promise<UserDTO[]>;
}
8 changes: 8 additions & 0 deletions backend/typescript/services/interfaces/petService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ export interface IPetService {
*/
deletePet(id: string): Promise<string>;

/**
* get all users in which the pet can be taken care of
* @param userColorLevel user's color level
* @returns list of pets
* @throws Error if matching fails
*/
getMatchingPetsForUser(userColorLevel: number): Promise<PetResponseDTO[]>;

/**
* retrieve all Pets that match given filter criteria
* @param query Pet queries
Expand Down
8 changes: 8 additions & 0 deletions backend/typescript/services/interfaces/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ interface IUserService {
* @throws Error if user deletion fails
*/
deleteUserByEmail(email: string): Promise<void>;

/**
* Get all pets that a user can take care of
* @param petColorLevel pet's color level
* @returns list of users
* @throws Error if failed to get matching pets
*/
getMatchingUsersForPet(petColorLevel: number): Promise<UserDTO[]>;
}

export default IUserService;
Loading