From 2217296f92c41f5bce28f4cab8bc501ea72a0246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Balsema=CC=83o?= Date: Fri, 10 May 2024 15:50:59 -0300 Subject: [PATCH 1/3] feat: add `top` endpoint to supplies --- src/supply/supply.controller.ts | 18 ++++++++++++++++++ src/supply/supply.service.ts | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 55133484..94f6779e 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -7,6 +7,7 @@ import { Param, Post, Put, + Query, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -31,6 +32,23 @@ export class SupplyController { } } + @Get('top') + async top(@Query('limit') limit: number = 10, @Query('skip') skip: number) { + try { + if (limit && typeof limit === 'string') { + limit = Number.parseInt(limit); + } + if (skip && typeof skip === 'string') { + skip = Number.parseInt(skip); + } + const data = await this.supplyServices.top(limit, skip); + return new ServerResponse(200, 'Successfully get top supplies', data); + } catch (err: any) { + this.logger.error(`Failed to get supplies: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } + @Post('') async store(@Body() body) { try { diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index a3caf57e..371c1465 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -53,4 +53,22 @@ export class SupplyService { return data; } + + async top(top: number = 10, skip: number = 0) { + const data = await this.prismaService.supply.groupBy({ + by: ['name'], + _count: { + name: true, + }, + orderBy: { + _count: { + name: 'desc', + }, + }, + skip: skip ?? 0, + take: top ?? 10, + }); + + return data.map((row) => ({ name: row.name, amount: row._count.name })); + } } From cd3dc54fe46c3fe0cdc94af1b7c8d08bfce04ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Balsema=CC=83o?= Date: Fri, 10 May 2024 16:18:54 -0300 Subject: [PATCH 2/3] fix: use ParseIntPipes to check query params --- src/supply/supply.controller.ts | 15 +++++++-------- src/supply/supply.service.ts | 16 ++++++++++++---- src/supply/types.ts | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 94f6779e..ea829682 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -1,10 +1,12 @@ import { Body, Controller, + DefaultValuePipe, Get, HttpException, Logger, Param, + ParseIntPipe, Post, Put, Query, @@ -33,15 +35,12 @@ export class SupplyController { } @Get('top') - async top(@Query('limit') limit: number = 10, @Query('skip') skip: number) { + async top( + @Query('perPage', new DefaultValuePipe(10), ParseIntPipe) perPage: number, + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, + ) { try { - if (limit && typeof limit === 'string') { - limit = Number.parseInt(limit); - } - if (skip && typeof skip === 'string') { - skip = Number.parseInt(skip); - } - const data = await this.supplyServices.top(limit, skip); + const data = await this.supplyServices.top({ perPage, page }); return new ServerResponse(200, 'Successfully get top supplies', data); } catch (err: any) { this.logger.error(`Failed to get supplies: ${err}`); diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index 371c1465..516638be 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -2,7 +2,11 @@ import z from 'zod'; import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateSupplySchema, UpdateSupplySchema } from './types'; +import { + CreateSupplySchema, + SupplySearchSchema, + UpdateSupplySchema, +} from './types'; @Injectable() export class SupplyService { @@ -54,7 +58,11 @@ export class SupplyService { return data; } - async top(top: number = 10, skip: number = 0) { + async top(body: z.infer) { + const payload = SupplySearchSchema.parse(body); + const take = payload.perPage; + const skip = payload.perPage * (payload.page - 1); + const data = await this.prismaService.supply.groupBy({ by: ['name'], _count: { @@ -65,8 +73,8 @@ export class SupplyService { name: 'desc', }, }, - skip: skip ?? 0, - take: top ?? 10, + take, + skip, }); return data.map((row) => ({ name: row.name, amount: row._count.name })); diff --git a/src/supply/types.ts b/src/supply/types.ts index faef2d11..5b4b4d39 100644 --- a/src/supply/types.ts +++ b/src/supply/types.ts @@ -16,6 +16,14 @@ const SupplySchema = z.object({ updatedAt: z.string().nullable().optional(), }); +const SupplySearchSchema = z.object({ + 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), + ), +}); + const CreateSupplySchema = SupplySchema.omit({ id: true, createdAt: true, @@ -27,4 +35,10 @@ const UpdateSupplySchema = SupplySchema.pick({ supplyCategoryId: true, }).partial(); -export { SupplySchema, CreateSupplySchema, UpdateSupplySchema, SupplyPriority }; +export { + SupplySchema, + SupplySearchSchema, + CreateSupplySchema, + UpdateSupplySchema, + SupplyPriority, +}; From ead400cb78b50209995f96dc67d9ea132bc91e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Balsema=CC=83o?= Date: Fri, 10 May 2024 17:12:23 -0300 Subject: [PATCH 3/3] fix: improve top endpoint with shelterId filter --- src/supply/supply.controller.ts | 3 ++- src/supply/supply.service.ts | 28 +++++++++++++--------------- src/supply/types.ts | 1 + 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index ea829682..92600339 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -38,9 +38,10 @@ export class SupplyController { async top( @Query('perPage', new DefaultValuePipe(10), ParseIntPipe) perPage: number, @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, + @Query('shelterId') shelterId: string, ) { try { - const data = await this.supplyServices.top({ perPage, page }); + const data = await this.supplyServices.top({ perPage, page, shelterId }); return new ServerResponse(200, 'Successfully get top supplies', data); } catch (err: any) { this.logger.error(`Failed to get supplies: ${err}`); diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index 516638be..ee861079 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -1,6 +1,6 @@ import z from 'zod'; import { Injectable } from '@nestjs/common'; - +import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { CreateSupplySchema, @@ -63,20 +63,18 @@ export class SupplyService { const take = payload.perPage; const skip = payload.perPage * (payload.page - 1); - const data = await this.prismaService.supply.groupBy({ - by: ['name'], - _count: { - name: true, - }, - orderBy: { - _count: { - name: 'desc', - }, - }, - take, - skip, - }); + const query = Prisma.sql`SELECT name, count(*)::int as amount + FROM shelter_supplies + LEFT JOIN supplies ON shelter_supplies.supply_id = supplies.id + ${ + payload.shelterId + ? Prisma.sql`WHERE shelter_id = ${payload.shelterId}` + : Prisma.empty + } + GROUP BY name + ORDER BY amount DESC + LIMIT ${take} OFFSET ${skip}`; - return data.map((row) => ({ name: row.name, amount: row._count.name })); + return await this.prismaService.$queryRaw(query); } } diff --git a/src/supply/types.ts b/src/supply/types.ts index 5b4b4d39..f0086076 100644 --- a/src/supply/types.ts +++ b/src/supply/types.ts @@ -17,6 +17,7 @@ const SupplySchema = z.object({ }); const SupplySearchSchema = z.object({ + shelterId: z.string().nullable().optional(), page: z.preprocess((v) => +((v ?? '1') as string), z.number().min(1)), perPage: z.preprocess( (v) => +((v ?? '10') as string),