Skip to content

Commit

Permalink
Criação do endpoint para busca de cidades dos abrigos (SOS-RS#82)
Browse files Browse the repository at this point in the history
Co-authored-by: Pedro Perrone <psperrone@hotmail.com>
  • Loading branch information
2 people authored and filipepacheco committed May 15, 2024
1 parent ecb22d8 commit de7e56c
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 78 deletions.
5 changes: 5 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
Empty file removed src/prisma/hooks/shelter/index.ts
Empty file.
Empty file.
Empty file removed src/prisma/hooks/user/index.ts
Empty file.
Empty file.
113 changes: 61 additions & 52 deletions src/shelter/ShelterSearch.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,57 +17,54 @@ const defaultTagsData: ShelterTagInfo = {
};

class ShelterSearch {
private formProps: Partial<IFilterFormProps>;
private formProps: Partial<ShelterSearchProps>;
private prismaService: PrismaService;

constructor(
prismaService: PrismaService,
props: Partial<IFilterFormProps> = {},
props: Partial<ShelterSearchProps> = {},
) {
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(
Expand Down Expand Up @@ -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),
Expand All @@ -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<Pick<IFilterFormProps, 'tags'>> = {},
tagProps: Partial<Pick<ShelterSearchProps, 'tags'>> = {},
results: SearchShelterTagResponse[],
voluntaryIds: string[],
): SearchShelterTagResponse[] {
Expand Down
11 changes: 11 additions & 0 deletions src/shelter/shelter.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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')
@UseGuards(ApplyUser)
async show(@UserDecorator() user: any, @Param('id') id: string) {
Expand Down
45 changes: 37 additions & 8 deletions src/shelter/shelter.service.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 });

Expand All @@ -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,
petFriendly: true,
shelteredPeople: true,
Expand Down Expand Up @@ -171,7 +181,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: {
Expand Down
51 changes: 33 additions & 18 deletions src/shelter/types/search.types.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
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<typeof ShelterStatusSchema>;

const ShelterTagTypeSchema = z.enum([
'NeedVolunteers',
'NeedDonations',
'RemainingSupplies',
]);

const ShelterTagInfoSchema = z.record(
ShelterTagTypeSchema,
z.number().optional(),
);

export type ShelterTagType = z.infer<typeof ShelterTagTypeSchema>;

export type ShelterTagInfo = z.infer<typeof ShelterTagInfoSchema>;

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<typeof ShelterSearchPropsSchema>;

type AllowedShelterFields = Omit<Shelter, 'contact'>;

export type SearchShelterTagResponse = AllowedShelterFields & {
shelterSupplies: (ShelterSupply & { supply: Supply })[];
};

export type ShelterTagType =
| 'NeedVolunteers'
| 'NeedDonations'
| 'RemainingSupplies';

export type ShelterTagInfo = {
[key in ShelterTagType]?: number;
};
5 changes: 5 additions & 0 deletions src/shelter/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit de7e56c

Please sign in to comment.