From 13d2cea097fc7a240390d39d5fbf1a25adefae00 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 12 May 2024 16:09:48 -0300 Subject: [PATCH 1/4] Finish Get shelter coordinates from Maps Api MVP --- src/googleMaps/mapsApi.ts | 16 ++++++++++++++++ src/shelter/shelter.controller.ts | 9 ++++++++- src/utils/utils.ts | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/googleMaps/mapsApi.ts diff --git a/src/googleMaps/mapsApi.ts b/src/googleMaps/mapsApi.ts new file mode 100644 index 00000000..6ffbf692 --- /dev/null +++ b/src/googleMaps/mapsApi.ts @@ -0,0 +1,16 @@ + + +export class MapsApi { + static key: string = process.env.MAPS_API_KEY || ''; + static url: string = process.env.MAPS_API_URL || 'https://maps.googleapis.com/maps/api'; + + static async getCoordinates(address: string): Promise<{ lat: number; lng: number }> { + console.log(`${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`); + + return fetch( + `${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`, + ).then((res) => res.json()) + .then((res) => res['results'][0]['geometry']['location']); + } +} + diff --git a/src/shelter/shelter.controller.ts b/src/shelter/shelter.controller.ts index 097285e4..2b52c7f9 100644 --- a/src/shelter/shelter.controller.ts +++ b/src/shelter/shelter.controller.ts @@ -15,6 +15,7 @@ import { ApiTags } from '@nestjs/swagger'; import { ShelterService } from './shelter.service'; import { ServerResponse } from '../utils'; import { StaffGuard } from '@/guards/staff.guard'; +import { getShelterCoordinates } from '@/utils/utils'; @ApiTags('Abrigos') @Controller('shelters') @@ -46,9 +47,15 @@ export class ShelterController { } @Post('') - @UseGuards(StaffGuard) + //@UseGuards(StaffGuard) async store(@Body() body) { try { + await getShelterCoordinates(body) + .then((coords) => { + body.latitude = coords.lat; + body.longitude = coords.lng; + }); + const data = await this.shelterService.store(body); return new ServerResponse(200, 'Successfully created shelter', data); } catch (err: any) { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 772b3e12..b4d0c131 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,6 @@ import { Logger } from '@nestjs/common'; +import { Shelter } from '@prisma/client'; +import { MapsApi } from '../googleMaps/mapsApi' class ServerResponse { readonly message: string; @@ -75,10 +77,22 @@ function deepMerge(target: Record, source: Record) { } } +async function getShelterCoordinates(shelter: Shelter){ + const { address } = shelter; + let coordinates = MapsApi.getCoordinates(address); + coordinates.then((res) => { + shelter.latitude = res.lat; + shelter.longitude = res.lng; + }); + + return coordinates; +} + export { ServerResponse, removeNotNumbers, getSessionData, deepMerge, capitalize, + getShelterCoordinates }; From 353d616296dcd77c66fbd1eb5769c202d9f6a691 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 12 May 2024 19:05:42 -0300 Subject: [PATCH 2/4] Finish interaction with Google Maps API - Finish interaction with Google Maps API through src/googleMaps/mapsApi.ts. - Now retrieving Shelter coordinates from Google Maps API everytime a new shelter is created or a already existent shelter is updated by admin. - If API fetch is not working, it does not break the creation or update features. - Loading Maps API key from .env file. - Update env.example: Create MAPS_API_URL and MAPS_API_KEY variables. --- .env.example | 5 ++++- src/googleMaps/mapsApi.ts | 27 ++++++++++++++++++--------- src/shelter/shelter.controller.ts | 15 +++++---------- src/utils/index.ts | 2 ++ src/utils/utils.ts | 22 +++++++++++----------- 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index aedd7c2d..1a1d044f 100644 --- a/.env.example +++ b/.env.example @@ -9,4 +9,7 @@ DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_ SECRET_KEY= HOST=::0.0.0.0 -PORT=4000 \ No newline at end of file +PORT=4000 + +MAPS_API_KEY= +MAPS_API_URL=https://maps.googleapis.com/maps/api diff --git a/src/googleMaps/mapsApi.ts b/src/googleMaps/mapsApi.ts index 6ffbf692..084c076a 100644 --- a/src/googleMaps/mapsApi.ts +++ b/src/googleMaps/mapsApi.ts @@ -1,16 +1,25 @@ - - export class MapsApi { static key: string = process.env.MAPS_API_KEY || ''; static url: string = process.env.MAPS_API_URL || 'https://maps.googleapis.com/maps/api'; - static async getCoordinates(address: string): Promise<{ lat: number; lng: number }> { - console.log(`${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`); - - return fetch( - `${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`, - ).then((res) => res.json()) - .then((res) => res['results'][0]['geometry']['location']); + static async getCoordinates(address: string): Promise<{ lat: number | null ; lng: number | null }> { + try { + const response = await fetch(`${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`); + if (!response.ok) { + throw new Error(`[MAPS API] Failed to fetch coordinates. Status: ${response.status}`); + } + + const data = await response.json(); + const location = data?.results?.[0]?.geometry?.location; + if (!location || !location.lat || !location.lng) { + throw new Error('Invalid response from maps API'); + } + + return location; + } catch (error: any) { + console.error(`[MAPS API] Error fetching coordinates: ${error.message}`); + return { lat: null, lng: null }; + } } } diff --git a/src/shelter/shelter.controller.ts b/src/shelter/shelter.controller.ts index 2b52c7f9..8f5af6ab 100644 --- a/src/shelter/shelter.controller.ts +++ b/src/shelter/shelter.controller.ts @@ -13,9 +13,8 @@ import { import { ApiTags } from '@nestjs/swagger'; import { ShelterService } from './shelter.service'; -import { ServerResponse } from '../utils'; +import { ServerResponse, fetchShelterCoordinates } from '../utils'; import { StaffGuard } from '@/guards/staff.guard'; -import { getShelterCoordinates } from '@/utils/utils'; @ApiTags('Abrigos') @Controller('shelters') @@ -47,22 +46,17 @@ export class ShelterController { } @Post('') - //@UseGuards(StaffGuard) + @UseGuards(StaffGuard) async store(@Body() body) { try { - await getShelterCoordinates(body) - .then((coords) => { - body.latitude = coords.lat; - body.longitude = coords.lng; - }); - + await fetchShelterCoordinates(body); const data = await this.shelterService.store(body); return new ServerResponse(200, 'Successfully created shelter', data); } catch (err: any) { this.logger.error(`Failed to create shelter: ${err}`); throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); } - } +} @Put(':id') async update(@Param('id') id: string, @Body() body) { @@ -79,6 +73,7 @@ export class ShelterController { @UseGuards(StaffGuard) async fullUpdate(@Param('id') id: string, @Body() body) { try { + await fetchShelterCoordinates(body); const data = await this.shelterService.fullUpdate(id, body); return new ServerResponse(200, 'Successfully updated shelter', data); } catch (err: any) { diff --git a/src/utils/index.ts b/src/utils/index.ts index 09934e74..8b83600a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,6 +4,7 @@ import { getSessionData, deepMerge, capitalize, + fetchShelterCoordinates } from './utils'; export { @@ -12,4 +13,5 @@ export { removeNotNumbers, getSessionData, deepMerge, + fetchShelterCoordinates }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b4d0c131..1895ce42 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -76,16 +76,16 @@ function deepMerge(target: Record, source: Record) { return source; } } - -async function getShelterCoordinates(shelter: Shelter){ - const { address } = shelter; - let coordinates = MapsApi.getCoordinates(address); - coordinates.then((res) => { - shelter.latitude = res.lat; - shelter.longitude = res.lng; - }); - - return coordinates; +async function fetchShelterCoordinates(shelter: Shelter) { + try { + const { address } = shelter; + const coordinates = await MapsApi.getCoordinates(address); + shelter.latitude = coordinates.lat; + shelter.longitude = coordinates.lng; + } catch (error) { + console.error(`Failed to fetch coordinates for shelter: ${error}`); + console.log("Failed to fetch coordinates for shelter: ", error); + } } export { @@ -94,5 +94,5 @@ export { getSessionData, deepMerge, capitalize, - getShelterCoordinates + fetchShelterCoordinates, }; From 379a39883ecf8088d41906cbf248bee9e39e0c71 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 13 May 2024 01:24:26 -0300 Subject: [PATCH 3/4] Fix: move fetch shelter coords to shelter service Move the concern of retrieving the shelter coords from api and updating payload, from controller to shelter.service --- src/googleMaps/mapsApi.ts | 18 ++++++++++++------ src/shelter/shelter.controller.ts | 6 ++---- src/shelter/shelter.service.ts | 19 +++++++++++++++++++ src/shelter/types/types.ts | 17 +++++++++++++++++ src/utils/index.ts | 4 ++-- src/utils/utils.ts | 13 +++++-------- 6 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/googleMaps/mapsApi.ts b/src/googleMaps/mapsApi.ts index 084c076a..bf032646 100644 --- a/src/googleMaps/mapsApi.ts +++ b/src/googleMaps/mapsApi.ts @@ -1,14 +1,21 @@ export class MapsApi { static key: string = process.env.MAPS_API_KEY || ''; - static url: string = process.env.MAPS_API_URL || 'https://maps.googleapis.com/maps/api'; + static url: string = + process.env.MAPS_API_URL || 'https://maps.googleapis.com/maps/api'; - static async getCoordinates(address: string): Promise<{ lat: number | null ; lng: number | null }> { + static async getCoordinates( + address: string, + ): Promise<{ lat: number | null; lng: number | null }> { try { - const response = await fetch(`${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`); + const response = await fetch( + `${MapsApi.url}/geocode/json?address=${encodeURI(address)}&key=${MapsApi.key}`, + ); if (!response.ok) { - throw new Error(`[MAPS API] Failed to fetch coordinates. Status: ${response.status}`); + throw new Error( + `[MAPS API] Failed to fetch coordinates. Status: ${response.status}`, + ); } - + const data = await response.json(); const location = data?.results?.[0]?.geometry?.location; if (!location || !location.lat || !location.lng) { @@ -22,4 +29,3 @@ export class MapsApi { } } } - diff --git a/src/shelter/shelter.controller.ts b/src/shelter/shelter.controller.ts index 8f5af6ab..097285e4 100644 --- a/src/shelter/shelter.controller.ts +++ b/src/shelter/shelter.controller.ts @@ -13,7 +13,7 @@ import { import { ApiTags } from '@nestjs/swagger'; import { ShelterService } from './shelter.service'; -import { ServerResponse, fetchShelterCoordinates } from '../utils'; +import { ServerResponse } from '../utils'; import { StaffGuard } from '@/guards/staff.guard'; @ApiTags('Abrigos') @@ -49,14 +49,13 @@ export class ShelterController { @UseGuards(StaffGuard) async store(@Body() body) { try { - await fetchShelterCoordinates(body); const data = await this.shelterService.store(body); return new ServerResponse(200, 'Successfully created shelter', data); } catch (err: any) { this.logger.error(`Failed to create shelter: ${err}`); throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); } -} + } @Put(':id') async update(@Param('id') id: string, @Body() body) { @@ -73,7 +72,6 @@ export class ShelterController { @UseGuards(StaffGuard) async fullUpdate(@Param('id') id: string, @Body() body) { try { - await fetchShelterCoordinates(body); const data = await this.shelterService.fullUpdate(id, body); return new ServerResponse(200, 'Successfully updated shelter', data); } catch (err: any) { diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index a24aa7ab..17505909 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -14,6 +14,7 @@ import { SearchSchema } from '../types'; import { ShelterSearch, parseTagResponse } from './ShelterSearch'; import { SupplyPriority } from '../supply/types'; import { IFilterFormProps } from './types/search.types'; +import { fetchShelterCoordinates } from '../utils'; @Injectable() export class ShelterService { @@ -26,6 +27,14 @@ export class ShelterService { async store(body: z.infer) { const payload = CreateShelterSchema.parse(body); + const { latitude, longitude } = await fetchShelterCoordinates( + payload.address, + ); + if (latitude && longitude) { + payload.latitude = latitude; + payload.longitude = longitude; + } + await this.prismaService.shelter.create({ data: { ...payload, @@ -49,6 +58,16 @@ export class ShelterService { async fullUpdate(id: string, body: z.infer) { const payload = FullUpdateShelterSchema.parse(body); + if (payload.address) { + const { latitude, longitude } = await fetchShelterCoordinates( + payload.address, + ); + if (latitude && longitude) { + payload.latitude = latitude; + payload.longitude = longitude; + } + } + await this.prismaService.shelter.update({ where: { id, diff --git a/src/shelter/types/types.ts b/src/shelter/types/types.ts index b3911c96..67e79f03 100644 --- a/src/shelter/types/types.ts +++ b/src/shelter/types/types.ts @@ -41,9 +41,26 @@ const FullUpdateShelterSchema = ShelterSchema.omit({ updatedAt: true, }).partial(); +interface ShelterData { + id: string; + name: string; + pix: string | null; + address: string; + petFriendly: boolean | null; + shelteredPeople: number | null; + latitude: number | null; + longitude: number | null; + capacity: number | null; + contact: string | null; + verified: boolean; + createdAt: string; + updatedAt: string | null; +} + export { ShelterSchema, CreateShelterSchema, UpdateShelterSchema, FullUpdateShelterSchema, + ShelterData, }; diff --git a/src/utils/index.ts b/src/utils/index.ts index 8b83600a..7a2c83f5 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,7 +4,7 @@ import { getSessionData, deepMerge, capitalize, - fetchShelterCoordinates + fetchShelterCoordinates, } from './utils'; export { @@ -13,5 +13,5 @@ export { removeNotNumbers, getSessionData, deepMerge, - fetchShelterCoordinates + fetchShelterCoordinates, }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1895ce42..c8c87e0d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,6 +1,5 @@ import { Logger } from '@nestjs/common'; -import { Shelter } from '@prisma/client'; -import { MapsApi } from '../googleMaps/mapsApi' +import { MapsApi } from '../googleMaps/mapsApi'; class ServerResponse { readonly message: string; @@ -76,15 +75,13 @@ function deepMerge(target: Record, source: Record) { return source; } } -async function fetchShelterCoordinates(shelter: Shelter) { +async function fetchShelterCoordinates(address: string) { try { - const { address } = shelter; - const coordinates = await MapsApi.getCoordinates(address); - shelter.latitude = coordinates.lat; - shelter.longitude = coordinates.lng; + const { lat, lng } = await MapsApi.getCoordinates(address); + return { latitude: lat, longitude: lng }; } catch (error) { console.error(`Failed to fetch coordinates for shelter: ${error}`); - console.log("Failed to fetch coordinates for shelter: ", error); + return { latitude: null, longitude: null }; } } From 05404e47e37a0570ebc10928712c9e77a84af36f Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 13 May 2024 13:01:42 -0300 Subject: [PATCH 4/4] refactor: Move update coordinates logic to private method This commit refactors the shelter service by moving the logic for updating coordinates into a private method. This eliminates repetitive code in both the store and fullUpdate methods. --- src/shelter/shelter.service.ts | 48 +++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index d6a8de01..ba67618f 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -20,21 +20,40 @@ import { fetchShelterCoordinates } from '../utils'; export class ShelterService { private voluntaryIds: string[] = []; - constructor(private readonly prismaService: PrismaService) { - this.loadVoluntaryIds(); - } - - async store(body: z.infer) { - const payload = CreateShelterSchema.parse(body); + private async generatePayloadWithCoordinates( + payload: + | z.infer + | z.infer, + ) { + if ((payload.latitude && payload.longitude) || !payload.address) { + return payload; + } const { latitude, longitude } = await fetchShelterCoordinates( payload.address, ); + + const updatedPayload = { + ...payload, + }; + if (latitude && longitude) { - payload.latitude = latitude; - payload.longitude = longitude; + updatedPayload.latitude = latitude; + updatedPayload.longitude = longitude; } + return updatedPayload; + } + + constructor(private readonly prismaService: PrismaService) { + this.loadVoluntaryIds(); + } + + async store(body: z.infer) { + const payload = CreateShelterSchema.parse( + await this.generatePayloadWithCoordinates(body), + ); + await this.prismaService.shelter.create({ data: { ...payload, @@ -57,16 +76,9 @@ export class ShelterService { } async fullUpdate(id: string, body: z.infer) { - const payload = FullUpdateShelterSchema.parse(body); - if (payload.address) { - const { latitude, longitude } = await fetchShelterCoordinates( - payload.address, - ); - if (latitude && longitude) { - payload.latitude = latitude; - payload.longitude = longitude; - } - } + const payload = FullUpdateShelterSchema.parse( + await this.generatePayloadWithCoordinates(body), + ); await this.prismaService.shelter.update({ where: {