Skip to content

Commit e653015

Browse files
AndersonCRochapedroperrone
authored andcommitted
Criação do endpoint para busca de cidades dos abrigos (SOS-RS#82)
Co-authored-by: Pedro Perrone <[email protected]>
1 parent 894556a commit e653015

File tree

10 files changed

+152
-78
lines changed

10 files changed

+152
-78
lines changed

prisma/schema.prisma

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ model Shelter {
8989
name String @unique
9090
pix String? @unique
9191
address String
92+
street String?
93+
neighbourhood String?
94+
city String?
95+
streetNumber String? @map("street_number")
96+
zipCode String? @map("zip_code")
9297
petFriendly Boolean? @map("pet_friendly")
9398
shelteredPeople Int? @map("sheltered_people")
9499
capacity Int?

src/prisma/hooks/shelter/index.ts

Whitespace-only changes.

src/prisma/hooks/shelter/shelter-hooks.ts

Whitespace-only changes.

src/prisma/hooks/user/index.ts

Whitespace-only changes.

src/prisma/hooks/user/user-hooks.ts

Whitespace-only changes.

src/shelter/ShelterSearch.ts

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Prisma } from '@prisma/client';
22

3-
import { PrismaService } from '../prisma/prisma.service';
43
import { SupplyPriority } from 'src/supply/types';
4+
import { PrismaService } from '../prisma/prisma.service';
55
import {
6-
IFilterFormProps,
76
SearchShelterTagResponse,
7+
ShelterSearchProps,
8+
ShelterStatus,
89
ShelterTagInfo,
910
ShelterTagType,
1011
} from './types/search.types';
@@ -16,57 +17,54 @@ const defaultTagsData: ShelterTagInfo = {
1617
};
1718

1819
class ShelterSearch {
19-
private formProps: Partial<IFilterFormProps>;
20+
private formProps: Partial<ShelterSearchProps>;
2021
private prismaService: PrismaService;
2122

2223
constructor(
2324
prismaService: PrismaService,
24-
props: Partial<IFilterFormProps> = {},
25+
props: Partial<ShelterSearchProps> = {},
2526
) {
2627
this.prismaService = prismaService;
2728
this.formProps = { ...props };
2829
}
2930

3031
priority(supplyIds: string[] = []): Prisma.ShelterWhereInput {
31-
if (this.formProps.priority) {
32-
return {
33-
shelterSupplies: {
34-
some: {
35-
priority: +this.formProps.priority,
36-
supplyId:
37-
supplyIds.length > 0
38-
? {
39-
in: supplyIds,
40-
}
41-
: undefined,
42-
},
32+
if (!this.formProps.priority) return {};
33+
34+
return {
35+
shelterSupplies: {
36+
some: {
37+
priority: +this.formProps.priority,
38+
supplyId:
39+
supplyIds.length > 0
40+
? {
41+
in: supplyIds,
42+
}
43+
: undefined,
4344
},
44-
};
45-
} else return {};
45+
},
46+
};
4647
}
4748

4849
get shelterStatus(): Prisma.ShelterWhereInput[] {
4950
if (!this.formProps.shelterStatus) return [];
50-
else {
51-
return this.formProps.shelterStatus.map((status) => {
52-
if (status === 'waiting')
53-
return {
54-
capacity: null,
55-
};
56-
else if (status === 'available')
57-
return {
58-
capacity: {
59-
gt: this.prismaService.shelter.fields.shelteredPeople,
60-
},
61-
};
62-
else
63-
return {
64-
capacity: {
65-
lte: this.prismaService.shelter.fields.shelteredPeople,
66-
},
67-
};
68-
});
69-
}
51+
52+
const clausesFromStatus: Record<
53+
ShelterStatus,
54+
Prisma.ShelterWhereInput['capacity'] | null
55+
> = {
56+
waiting: null,
57+
available: {
58+
gt: this.prismaService.shelter.fields.shelteredPeople,
59+
},
60+
unavailable: {
61+
lte: this.prismaService.shelter.fields.shelteredPeople,
62+
},
63+
};
64+
65+
return this.formProps.shelterStatus.map((status) => ({
66+
capacity: clausesFromStatus[status],
67+
}));
7068
}
7169

7270
supplyCategoryIds(
@@ -104,27 +102,38 @@ class ShelterSearch {
104102

105103
get search(): Prisma.ShelterWhereInput[] {
106104
if (!this.formProps.search) return [];
107-
else
108-
return [
109-
{
110-
address: {
111-
contains: this.formProps.search,
112-
mode: 'insensitive',
113-
},
105+
106+
return [
107+
{
108+
address: {
109+
contains: this.formProps.search,
110+
mode: 'insensitive',
114111
},
115-
{
116-
name: {
117-
contains: this.formProps.search,
118-
mode: 'insensitive',
119-
},
112+
},
113+
{
114+
name: {
115+
contains: this.formProps.search,
116+
mode: 'insensitive',
120117
},
121-
];
118+
},
119+
];
120+
}
121+
122+
get cities(): Prisma.ShelterWhereInput {
123+
if (!this.formProps.cities) return {};
124+
125+
return {
126+
city: {
127+
in: this.formProps.cities,
128+
},
129+
};
122130
}
123131

124132
get query(): Prisma.ShelterWhereInput {
125133
if (Object.keys(this.formProps).length === 0) return {};
126134
const queryData = {
127135
AND: [
136+
this.cities,
128137
{ OR: this.search },
129138
{ OR: this.shelterStatus },
130139
this.priority(this.formProps.supplyIds),
@@ -144,7 +153,7 @@ class ShelterSearch {
144153
* @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
145154
*/
146155
function parseTagResponse(
147-
tagProps: Partial<Pick<IFilterFormProps, 'tags'>> = {},
156+
tagProps: Partial<Pick<ShelterSearchProps, 'tags'>> = {},
148157
results: SearchShelterTagResponse[],
149158
voluntaryIds: string[],
150159
): SearchShelterTagResponse[] {

src/shelter/shelter.controller.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ export class ShelterController {
3636
}
3737
}
3838

39+
@Get('cities')
40+
async cities() {
41+
try {
42+
const data = await this.shelterService.getCities();
43+
return new ServerResponse(200, 'Successfully get shelters cities', data);
44+
} catch (err: any) {
45+
this.logger.error(`Failed to get shelters cities: ${err}`);
46+
throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400);
47+
}
48+
}
49+
3950
@Get(':id')
4051
@UseGuards(ApplyUser)
4152
async show(@UserDecorator() user: any, @Param('id') id: string) {

src/shelter/shelter.service.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { z } from 'zod';
21
import { Injectable } from '@nestjs/common';
3-
import * as qs from 'qs';
42
import { Prisma } from '@prisma/client';
53
import { DefaultArgs } from '@prisma/client/runtime/library';
4+
import * as qs from 'qs';
5+
import { z } from 'zod';
66

77
import { PrismaService } from '../prisma/prisma.service';
8+
import { SupplyPriority } from '../supply/types';
9+
import { SearchSchema } from '../types';
10+
import { ShelterSearch, parseTagResponse } from './ShelterSearch';
11+
import { ShelterSearchPropsSchema } from './types/search.types';
812
import {
913
CreateShelterSchema,
1014
FullUpdateShelterSchema,
1115
UpdateShelterSchema,
1216
} from './types/types';
13-
import { SearchSchema } from '../types';
14-
import { ShelterSearch, parseTagResponse } from './ShelterSearch';
15-
import { SupplyPriority } from '../supply/types';
16-
import { IFilterFormProps } from './types/search.types';
1717

1818
@Injectable()
1919
export class ShelterService {
@@ -69,6 +69,11 @@ export class ShelterService {
6969
id: true,
7070
name: true,
7171
address: true,
72+
street: true,
73+
neighbourhood: true,
74+
city: true,
75+
streetNumber: true,
76+
zipCode: true,
7277
pix: true,
7378
shelteredPeople: true,
7479
capacity: true,
@@ -115,7 +120,7 @@ export class ShelterService {
115120
perPage,
116121
search: searchQuery,
117122
} = SearchSchema.parse(query);
118-
const queryData = qs.parse(searchQuery) as unknown as IFilterFormProps;
123+
const queryData = ShelterSearchPropsSchema.parse(qs.parse(searchQuery));
119124
const { query: where } = new ShelterSearch(this.prismaService, queryData);
120125
const count = await this.prismaService.shelter.count({ where });
121126

@@ -136,6 +141,11 @@ export class ShelterService {
136141
name: true,
137142
pix: true,
138143
address: true,
144+
street: true,
145+
neighbourhood: true,
146+
city: true,
147+
streetNumber: true,
148+
zipCode: true,
139149
capacity: true,
140150
petFriendly: true,
141151
shelteredPeople: true,
@@ -171,7 +181,26 @@ export class ShelterService {
171181
};
172182
}
173183

174-
loadVoluntaryIds() {
184+
async getCities() {
185+
const cities = await this.prismaService.shelter.groupBy({
186+
by: ['city'],
187+
_count: {
188+
id: true,
189+
},
190+
orderBy: {
191+
_count: {
192+
id: 'desc',
193+
},
194+
},
195+
});
196+
197+
return cities.map(({ city, _count: { id: sheltersCount } }) => ({
198+
city: city || 'Cidade não informada',
199+
sheltersCount,
200+
}));
201+
}
202+
203+
private loadVoluntaryIds() {
175204
this.prismaService.supplyCategory
176205
.findMany({
177206
where: {

src/shelter/types/search.types.ts

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
import { Shelter, ShelterSupply, Supply } from '@prisma/client';
2+
import { z } from 'zod';
23
import { SupplyPriority } from '../../supply/types';
34

4-
export type ShelterAvailabilityStatus = 'available' | 'unavailable' | 'waiting';
5+
const ShelterStatusSchema = z.enum(['available', 'unavailable', 'waiting']);
56

6-
export interface IFilterFormProps {
7-
search: string;
8-
priority: SupplyPriority | null;
9-
supplyCategoryIds: string[];
10-
supplyIds: string[];
11-
shelterStatus: ShelterAvailabilityStatus[];
12-
tags: ShelterTagInfo | null;
13-
}
7+
export type ShelterStatus = z.infer<typeof ShelterStatusSchema>;
8+
9+
const ShelterTagTypeSchema = z.enum([
10+
'NeedVolunteers',
11+
'NeedDonations',
12+
'RemainingSupplies',
13+
]);
14+
15+
const ShelterTagInfoSchema = z.record(
16+
ShelterTagTypeSchema,
17+
z.number().optional(),
18+
);
19+
20+
export type ShelterTagType = z.infer<typeof ShelterTagTypeSchema>;
21+
22+
export type ShelterTagInfo = z.infer<typeof ShelterTagInfoSchema>;
23+
24+
export const ShelterSearchPropsSchema = z.object({
25+
search: z.string().optional(),
26+
priority: z.preprocess(
27+
(value) => Number(value) || undefined,
28+
z.nativeEnum(SupplyPriority).optional(),
29+
),
30+
supplyCategoryIds: z.array(z.string()).optional(),
31+
supplyIds: z.array(z.string()).optional(),
32+
shelterStatus: z.array(ShelterStatusSchema).optional(),
33+
tags: ShelterTagInfoSchema.nullable().optional(),
34+
cities: z.array(z.string()).optional(),
35+
});
36+
37+
export type ShelterSearchProps = z.infer<typeof ShelterSearchPropsSchema>;
1438

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

1741
export type SearchShelterTagResponse = AllowedShelterFields & {
1842
shelterSupplies: (ShelterSupply & { supply: Supply })[];
1943
};
20-
21-
export type ShelterTagType =
22-
| 'NeedVolunteers'
23-
| 'NeedDonations'
24-
| 'RemainingSupplies';
25-
26-
export type ShelterTagInfo = {
27-
[key in ShelterTagType]?: number;
28-
};

src/shelter/types/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ const ShelterSchema = z.object({
1212
name: z.string().transform(capitalize),
1313
pix: z.string().nullable().optional(),
1414
address: z.string().transform(capitalize),
15+
city: z.string().transform(capitalize).nullable().optional(),
16+
neighbourhood: z.string().transform(capitalize).nullable().optional(),
17+
street: z.string().transform(capitalize).nullable().optional(),
18+
streetNumber: z.string().nullable().optional(),
19+
zipCode: z.string().nullable().optional(),
1520
petFriendly: z.boolean().nullable().optional(),
1621
shelteredPeople: z.number().min(0).nullable().optional(),
1722
latitude: z.number().nullable().optional(),

0 commit comments

Comments
 (0)