Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions backend/typescript/middlewares/validators/petValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,27 @@ export const petRequestDtoValidators = async (

// return next();
// };

export const matchPetsValidator = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const { userId } = req.params;

if (!userId) {
return res.status(400).json({ error: "userId parameter is required." });
}

if (typeof userId !== "string") {
return res
.status(400)
.json({ error: "userId parameter must be a string." });
}

if (Number.isNaN(Number(userId))) {
return res.status(400).json({ error: "Invalid user ID" });
}

return next();
};
22 changes: 22 additions & 0 deletions backend/typescript/middlewares/validators/userValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,25 @@ export const updateUserDtoValidator = async (
}
return next();
};

export const matchUsersValidator = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const { petId } = req.params;

if (!petId) {
return res.status(400).json({ error: "petId parameter is required." });
}

if (typeof petId !== "string") {
return res.status(400).json({ error: "petId parameter must be a string." });
}

if (Number.isNaN(Number(petId))) {
return res.status(400).json({ error: "Invalid pet ID" });
}

return next();
};
32 changes: 32 additions & 0 deletions backend/typescript/rest/petRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Router } from "express";
import {
petRequestDtoValidators,
matchPetsValidator,
/* // petFilterValidators, */
} from "../middlewares/validators/petValidators";
import PetService from "../services/implementations/petService";
Expand All @@ -11,9 +12,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 +145,31 @@ petRouter.get("/:id", async (req, res) => {
}
});

petRouter.get("/match/pets/:userId", matchPetsValidator, async (req, res) => {
const { userId } = req.params;
const contentType = req.headers["content-type"];

try {
const user: UserDTO = await userService.getUserById(userId);
const matchingPets = await petService.getMatchingPetsForUser(
user.colorLevel,
);
sendResponseByMimeType<PetResponseDTO>(res, 200, contentType, matchingPets);
} catch (error: unknown) {
if (error instanceof NotFoundError) {
sendResponseByMimeType(res, 400, contentType, [
{
error: getErrorMessage(error),
},
]);
} else {
sendResponseByMimeType(res, 500, contentType, [
{
error: getErrorMessage(error),
},
]);
}
}
});

export default petRouter;
31 changes: 31 additions & 0 deletions backend/typescript/rest/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getAccessToken } from "../middlewares/auth";
import {
createUserDtoValidator,
updateUserDtoValidator,
matchUsersValidator,
} from "../middlewares/validators/userValidators";
import nodemailerConfig from "../nodemailer.config";
import AuthService from "../services/implementations/authService";
Expand All @@ -19,12 +20,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 +329,31 @@ userRouter.delete("/", async (req, res) => {
.json({ error: "Must supply one of userId or email as query parameter." });
});

userRouter.get("/match/users/:petId", matchUsersValidator, async (req, res) => {
const { petId } = req.params;
const contentType = req.headers["content-type"];

try {
const pet: PetResponseDTO = await petService.getPet(petId);
const matchingUsers = await userService.getMatchingUsersForPet(
pet.colorLevel,
);
sendResponseByMimeType<UserDTO>(res, 200, contentType, matchingUsers);
} catch (error: unknown) {
if (error instanceof NotFoundError) {
sendResponseByMimeType(res, 400, contentType, [
{
error: getErrorMessage(error),
},
]);
} else {
sendResponseByMimeType(res, 500, contentType, [
{
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 pets the user can take 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 users that a pet 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