From 6f289ff18ca2f2d37c74d0cec0b3b8baf0448fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Pastorini?= Date: Sat, 11 May 2024 14:11:52 -0600 Subject: [PATCH 1/2] Endpoint to get matches of supply surplus and demand between shelters Co-authored-by: "Felipe Izaguirre" --- src/supply/supply.controller.ts | 18 +++++++++++++ src/supply/supply.service.ts | 48 ++++++++++++++++++++++++++++++++- src/supply/types.ts | 17 +++++++++++- 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 55133484..1269c8fe 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -1,12 +1,15 @@ import { Body, Controller, + DefaultValuePipe, Get, HttpException, Logger, Param, + ParseIntPipe, Post, Put, + Query, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -52,4 +55,19 @@ export class SupplyController { throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); } } + + @Get('surplus-demand-matches') + async surplus_demand_matches( + @Query('supplyId') supplyId: string, + @Query('perPage', new DefaultValuePipe(20), ParseIntPipe) perPage: number, + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, + ) { + try { + const data = await this.supplyServices.surplus_demand_matches({ supplyId, perPage, page }); + return new ServerResponse(200, 'Successfully got matches between surplus and demand', data); + } catch (err: any) { + this.logger.error(`Failed to get matches: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } } diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index a3caf57e..9e3bb355 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -1,8 +1,9 @@ import z from 'zod'; import { Injectable } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateSupplySchema, UpdateSupplySchema } from './types'; +import { CreateSupplySchema, SurplusDemandMatch, UpdateSupplySchema } from './types'; @Injectable() export class SupplyService { @@ -53,4 +54,49 @@ export class SupplyService { return data; } + + async surplus_demand_matches(body: z.infer) { + const payload = SurplusDemandMatch.parse(body); + const skip = payload.perPage * (payload.page - 1); + + const query = Prisma.sql` + WITH supply_surplus AS ( + SELECT + ss.supply_id, + sp.name AS supply_name, + s.id AS shelter_id, + s.name AS shelter_name + FROM shelter_supplies ss + INNER JOIN shelters s ON s.id = ss.shelter_id + INNER JOIN supplies sp ON sp.id = ss.supply_id + WHERE ss.priority IN (1) -- 1 is 'Remaining' + ), supply_demand AS ( + SELECT + ss.supply_id, + sp.name AS supply_name, + s.id AS shelter_id, + s.name AS shelter_name + FROM shelter_supplies ss + INNER JOIN shelters s ON s.id = ss.shelter_id + INNER JOIN supplies sp ON sp.id = ss.supply_id + WHERE ss.priority IN (10, 100) -- 10 & 100 are 'Needing' & 'Urgent' + ) + SELECT + spl.supply_id, + spl.supply_name, + spl.shelter_id AS shelter_from_id, + spl.shelter_name AS shelter_from_name, + sd.shelter_id AS shelter_to_id, + sd.shelter_name AS shelter_to_name + FROM supply_surplus spl + INNER JOIN supply_demand sd ON sd.supply_id = spl.supply_id + ${payload.supplyId + ? Prisma.sql`WHERE spl.supply_id = ${payload.supplyId}` + : Prisma.empty} + ORDER BY spl.supply_name DESC, shelter_from_name DESC, shelter_to_name DESC + LIMIT ${payload.perPage} OFFSET ${skip} + `; + + return await this.prismaService.$queryRaw(query); + } } diff --git a/src/supply/types.ts b/src/supply/types.ts index faef2d11..6da3d7fc 100644 --- a/src/supply/types.ts +++ b/src/supply/types.ts @@ -27,4 +27,19 @@ const UpdateSupplySchema = SupplySchema.pick({ supplyCategoryId: true, }).partial(); -export { SupplySchema, CreateSupplySchema, UpdateSupplySchema, SupplyPriority }; +const SurplusDemandMatch = z.object({ + supplyId: z.string().nullable().optional(), + page: z.preprocess((v) => +((v ?? '1') as string), z.number().min(1)), + perPage: z.preprocess( + (v) => +((v ?? '10') as string), + z.number().min(1).max(100), + ), +}); + +export { + SupplySchema, + CreateSupplySchema, + UpdateSupplySchema, + SurplusDemandMatch, + SupplyPriority +}; From 9b41dc011dc5876f6ca680811c6f7c70737502a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Pastorini?= Date: Sun, 12 May 2024 12:54:48 -0600 Subject: [PATCH 2/2] Add support for filtering by supply_id and shelter_id's Co-authored-by: "Felipe Izaguirre" --- src/supply/supply.controller.ts | 10 +++++++++- src/supply/supply.service.ts | 28 +++++++++++++++++++++++++--- src/supply/types.ts | 2 ++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 1269c8fe..1acca273 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -59,11 +59,19 @@ export class SupplyController { @Get('surplus-demand-matches') async surplus_demand_matches( @Query('supplyId') supplyId: string, + @Query('shelterIdSurplus') shelterIdSurplus: string, + @Query('shelterIdNeeded') shelterIdNeeded: string, @Query('perPage', new DefaultValuePipe(20), ParseIntPipe) perPage: number, @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, ) { try { - const data = await this.supplyServices.surplus_demand_matches({ supplyId, perPage, page }); + const data = await this.supplyServices.surplus_demand_matches({ + supplyId, + shelterIdSurplus, + shelterIdNeeded, + perPage, + page + }); return new ServerResponse(200, 'Successfully got matches between surplus and demand', data); } catch (err: any) { this.logger.error(`Failed to get matches: ${err}`); diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index 9e3bb355..bb83f5f1 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -59,6 +59,30 @@ export class SupplyService { const payload = SurplusDemandMatch.parse(body); const skip = payload.perPage * (payload.page - 1); + let where: Prisma.Sql[] = []; + + if (payload.supplyId) { + if (where.length == 0) { + where.push(Prisma.sql`WHERE spl.supply_id = ${payload.supplyId}`); + } else { + where.push(Prisma.sql`spl.supply_id = ${payload.supplyId}`); + } + } + if (payload.shelterIdSurplus) { + if (where.length == 0) { + where.push(Prisma.sql`WHERE spl.shelter_id = ${payload.shelterIdSurplus}`); + } else { + where.push(Prisma.sql`spl.shelter_id = ${payload.shelterIdSurplus}`); + } + } + if (payload.shelterIdNeeded) { + if (where.length == 0) { + where.push(Prisma.sql`WHERE sd.shelter_id = ${payload.shelterIdNeeded}`); + } else { + where.push(Prisma.sql`sd.shelter_id = ${payload.shelterIdNeeded}`); + } + } + const query = Prisma.sql` WITH supply_surplus AS ( SELECT @@ -90,9 +114,7 @@ export class SupplyService { sd.shelter_name AS shelter_to_name FROM supply_surplus spl INNER JOIN supply_demand sd ON sd.supply_id = spl.supply_id - ${payload.supplyId - ? Prisma.sql`WHERE spl.supply_id = ${payload.supplyId}` - : Prisma.empty} + ${where.length > 0 ? Prisma.join(where, ' AND ') : Prisma.empty} ORDER BY spl.supply_name DESC, shelter_from_name DESC, shelter_to_name DESC LIMIT ${payload.perPage} OFFSET ${skip} `; diff --git a/src/supply/types.ts b/src/supply/types.ts index 6da3d7fc..2e09b2fe 100644 --- a/src/supply/types.ts +++ b/src/supply/types.ts @@ -29,6 +29,8 @@ const UpdateSupplySchema = SupplySchema.pick({ const SurplusDemandMatch = z.object({ supplyId: z.string().nullable().optional(), + shelterIdSurplus: z.string().nullable().optional(), + shelterIdNeeded: z.string().nullable().optional(), page: z.preprocess((v) => +((v ?? '1') as string), z.number().min(1)), perPage: z.preprocess( (v) => +((v ?? '10') as string),