diff --git a/prisma/migrations/20240512005246_/migration.sql b/prisma/migrations/20240512005246_/migration.sql new file mode 100644 index 00000000..c57b86cf --- /dev/null +++ b/prisma/migrations/20240512005246_/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "shelters" ADD COLUMN "city" TEXT, +ADD COLUMN "neighbourhood" TEXT, +ADD COLUMN "street" TEXT, +ADD COLUMN "street_number" TEXT, +ADD COLUMN "zip_code" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 792b1f65..b853d2b4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -89,6 +89,11 @@ model Shelter { name String @unique pix String? @unique address String + street String? + neighbourhood String? + city String? + streetNumber String? @map("street_number") + zipCode String? @map("zip_code") petFriendly Boolean? @map("pet_friendly") shelteredPeople Int? @map("sheltered_people") capacity Int? diff --git a/src/prisma/hooks/shelter/index.ts b/src/prisma/hooks/shelter/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/prisma/hooks/shelter/shelter-hooks.ts b/src/prisma/hooks/shelter/shelter-hooks.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/prisma/hooks/user/index.ts b/src/prisma/hooks/user/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/prisma/hooks/user/user-hooks.ts b/src/prisma/hooks/user/user-hooks.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/shelter/ShelterSearch.ts b/src/shelter/ShelterSearch.ts index 7598b8e0..205f97e8 100644 --- a/src/shelter/ShelterSearch.ts +++ b/src/shelter/ShelterSearch.ts @@ -1,10 +1,11 @@ import { Prisma } from '@prisma/client'; -import { PrismaService } from '../prisma/prisma.service'; import { SupplyPriority } from 'src/supply/types'; +import { PrismaService } from '../prisma/prisma.service'; import { - IFilterFormProps, SearchShelterTagResponse, + ShelterSearchProps, + ShelterStatus, ShelterTagInfo, ShelterTagType, } from './types/search.types'; @@ -16,57 +17,54 @@ const defaultTagsData: ShelterTagInfo = { }; class ShelterSearch { - private formProps: Partial; + private formProps: Partial; private prismaService: PrismaService; constructor( prismaService: PrismaService, - props: Partial = {}, + props: Partial = {}, ) { this.prismaService = prismaService; this.formProps = { ...props }; } priority(supplyIds: string[] = []): Prisma.ShelterWhereInput { - if (this.formProps.priority) { - return { - shelterSupplies: { - some: { - priority: +this.formProps.priority, - supplyId: - supplyIds.length > 0 - ? { - in: supplyIds, - } - : undefined, - }, + if (!this.formProps.priority) return {}; + + return { + shelterSupplies: { + some: { + priority: +this.formProps.priority, + supplyId: + supplyIds.length > 0 + ? { + in: supplyIds, + } + : undefined, }, - }; - } else return {}; + }, + }; } get shelterStatus(): Prisma.ShelterWhereInput[] { if (!this.formProps.shelterStatus) return []; - else { - return this.formProps.shelterStatus.map((status) => { - if (status === 'waiting') - return { - capacity: null, - }; - else if (status === 'available') - return { - capacity: { - gt: this.prismaService.shelter.fields.shelteredPeople, - }, - }; - else - return { - capacity: { - lte: this.prismaService.shelter.fields.shelteredPeople, - }, - }; - }); - } + + const clausesFromStatus: Record< + ShelterStatus, + Prisma.ShelterWhereInput['capacity'] | null + > = { + waiting: null, + available: { + gt: this.prismaService.shelter.fields.shelteredPeople, + }, + unavailable: { + lte: this.prismaService.shelter.fields.shelteredPeople, + }, + }; + + return this.formProps.shelterStatus.map((status) => ({ + capacity: clausesFromStatus[status], + })); } supplyCategoryIds( @@ -104,27 +102,38 @@ class ShelterSearch { get search(): Prisma.ShelterWhereInput[] { if (!this.formProps.search) return []; - else - return [ - { - address: { - contains: this.formProps.search, - mode: 'insensitive', - }, + + return [ + { + address: { + contains: this.formProps.search, + mode: 'insensitive', }, - { - name: { - contains: this.formProps.search, - mode: 'insensitive', - }, + }, + { + name: { + contains: this.formProps.search, + mode: 'insensitive', }, - ]; + }, + ]; + } + + get cities(): Prisma.ShelterWhereInput { + if (!this.formProps.cities) return {}; + + return { + city: { + in: this.formProps.cities, + }, + }; } get query(): Prisma.ShelterWhereInput { if (Object.keys(this.formProps).length === 0) return {}; const queryData = { AND: [ + this.cities, { OR: this.search }, { OR: this.shelterStatus }, this.priority(this.formProps.supplyIds), @@ -144,7 +153,7 @@ class ShelterSearch { * @returns Retorna a lista de resultados, adicionando o campo tags em cada supply para assim categoriza-los corretamente e limitar a quantidade de cada retornada respeitando os parametros em formProps */ function parseTagResponse( - tagProps: Partial> = {}, + tagProps: Partial> = {}, results: SearchShelterTagResponse[], voluntaryIds: string[], ): SearchShelterTagResponse[] { diff --git a/src/shelter/shelter.controller.ts b/src/shelter/shelter.controller.ts index 097285e4..b34b96a7 100644 --- a/src/shelter/shelter.controller.ts +++ b/src/shelter/shelter.controller.ts @@ -34,6 +34,17 @@ export class ShelterController { } } + @Get('cities') + async cities() { + try { + const data = await this.shelterService.getCities(); + return new ServerResponse(200, 'Successfully get shelters cities', data); + } catch (err: any) { + this.logger.error(`Failed to get shelters cities: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } + @Get(':id') async show(@Param('id') id: string) { try { diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index a24aa7ab..b3b7f7fe 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -1,19 +1,19 @@ -import { z } from 'zod'; import { Injectable } from '@nestjs/common'; -import * as qs from 'qs'; import { Prisma } from '@prisma/client'; import { DefaultArgs } from '@prisma/client/runtime/library'; +import * as qs from 'qs'; +import { z } from 'zod'; import { PrismaService } from '../prisma/prisma.service'; +import { SupplyPriority } from '../supply/types'; +import { SearchSchema } from '../types'; +import { ShelterSearch, parseTagResponse } from './ShelterSearch'; +import { ShelterSearchPropsSchema } from './types/search.types'; import { CreateShelterSchema, FullUpdateShelterSchema, UpdateShelterSchema, } from './types/types'; -import { SearchSchema } from '../types'; -import { ShelterSearch, parseTagResponse } from './ShelterSearch'; -import { SupplyPriority } from '../supply/types'; -import { IFilterFormProps } from './types/search.types'; @Injectable() export class ShelterService { @@ -69,6 +69,11 @@ export class ShelterService { id: true, name: true, address: true, + street: true, + neighbourhood: true, + city: true, + streetNumber: true, + zipCode: true, pix: true, shelteredPeople: true, capacity: true, @@ -115,7 +120,7 @@ export class ShelterService { perPage, search: searchQuery, } = SearchSchema.parse(query); - const queryData = qs.parse(searchQuery) as unknown as IFilterFormProps; + const queryData = ShelterSearchPropsSchema.parse(qs.parse(searchQuery)); const { query: where } = new ShelterSearch(this.prismaService, queryData); const count = await this.prismaService.shelter.count({ where }); @@ -136,6 +141,11 @@ export class ShelterService { name: true, pix: true, address: true, + street: true, + neighbourhood: true, + city: true, + streetNumber: true, + zipCode: true, capacity: true, contact: true, petFriendly: true, @@ -172,7 +182,26 @@ export class ShelterService { }; } - loadVoluntaryIds() { + async getCities() { + const cities = await this.prismaService.shelter.groupBy({ + by: ['city'], + _count: { + id: true, + }, + orderBy: { + _count: { + id: 'desc', + }, + }, + }); + + return cities.map(({ city, _count: { id: sheltersCount } }) => ({ + city: city || 'Cidade não informada', + sheltersCount, + })); + } + + private loadVoluntaryIds() { this.prismaService.supplyCategory .findMany({ where: { diff --git a/src/shelter/types/search.types.ts b/src/shelter/types/search.types.ts index b80ca59d..8566df7d 100644 --- a/src/shelter/types/search.types.ts +++ b/src/shelter/types/search.types.ts @@ -1,26 +1,41 @@ import { Shelter, ShelterSupply, Supply } from '@prisma/client'; +import { z } from 'zod'; import { SupplyPriority } from '../../supply/types'; -export type ShelterAvailabilityStatus = 'available' | 'unavailable' | 'waiting'; +const ShelterStatusSchema = z.enum(['available', 'unavailable', 'waiting']); -export interface IFilterFormProps { - search: string; - priority: SupplyPriority | null; - supplyCategoryIds: string[]; - supplyIds: string[]; - shelterStatus: ShelterAvailabilityStatus[]; - tags: ShelterTagInfo | null; -} +export type ShelterStatus = z.infer; -export type SearchShelterTagResponse = Shelter & { - shelterSupplies: (ShelterSupply & { supply: Supply })[]; -}; +const ShelterTagTypeSchema = z.enum([ + 'NeedVolunteers', + 'NeedDonations', + 'RemainingSupplies', +]); + +const ShelterTagInfoSchema = z.record( + ShelterTagTypeSchema, + z.number().optional(), +); + +export type ShelterTagType = z.infer; -export type ShelterTagType = - | 'NeedVolunteers' - | 'NeedDonations' - | 'RemainingSupplies'; +export type ShelterTagInfo = z.infer; -export type ShelterTagInfo = { - [key in ShelterTagType]?: number; +export const ShelterSearchPropsSchema = z.object({ + search: z.string().optional(), + priority: z.preprocess( + (value) => Number(value) || undefined, + z.nativeEnum(SupplyPriority).optional(), + ), + supplyCategoryIds: z.array(z.string()).optional(), + supplyIds: z.array(z.string()).optional(), + shelterStatus: z.array(ShelterStatusSchema).optional(), + tags: ShelterTagInfoSchema.nullable().optional(), + cities: z.array(z.string()).optional(), +}); + +export type ShelterSearchProps = z.infer; + +export type SearchShelterTagResponse = Shelter & { + shelterSupplies: (ShelterSupply & { supply: Supply })[]; }; diff --git a/src/shelter/types/types.ts b/src/shelter/types/types.ts index b3911c96..0dc21bac 100644 --- a/src/shelter/types/types.ts +++ b/src/shelter/types/types.ts @@ -12,6 +12,11 @@ const ShelterSchema = z.object({ name: z.string().transform(capitalize), pix: z.string().nullable().optional(), address: z.string().transform(capitalize), + city: z.string().transform(capitalize).nullable().optional(), + neighbourhood: z.string().transform(capitalize).nullable().optional(), + street: z.string().transform(capitalize).nullable().optional(), + streetNumber: z.string().nullable().optional(), + zipCode: z.string().nullable().optional(), petFriendly: z.boolean().nullable().optional(), shelteredPeople: z.number().min(0).nullable().optional(), latitude: z.number().nullable().optional(),