Skip to content

Criação do endpoint para busca de cidades dos abrigos #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions prisma/migrations/20240512005246_/migration.sql
Original file line number Diff line number Diff line change
@@ -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;
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 @@ -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 {
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,
contact: true,
petFriendly: true,
Expand Down Expand Up @@ -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: {
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,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<typeof ShelterStatusSchema>;

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

export type ShelterTagType =
| 'NeedVolunteers'
| 'NeedDonations'
| 'RemainingSupplies';
export type ShelterTagInfo = z.infer<typeof ShelterTagInfoSchema>;

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

export type SearchShelterTagResponse = Shelter & {
shelterSupplies: (ShelterSupply & { supply: Supply })[];
};
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