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 new file mode 100644 index 00000000..bf032646 --- /dev/null +++ b/src/googleMaps/mapsApi.ts @@ -0,0 +1,31 @@ +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 | 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.service.ts b/src/shelter/shelter.service.ts index a320db35..ba67618f 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -14,17 +14,45 @@ 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 { private voluntaryIds: string[] = []; + 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) { + updatedPayload.latitude = latitude; + updatedPayload.longitude = longitude; + } + + return updatedPayload; + } + constructor(private readonly prismaService: PrismaService) { this.loadVoluntaryIds(); } async store(body: z.infer) { - const payload = CreateShelterSchema.parse(body); + const payload = CreateShelterSchema.parse( + await this.generatePayloadWithCoordinates(body), + ); await this.prismaService.shelter.create({ data: { @@ -48,7 +76,10 @@ export class ShelterService { } async fullUpdate(id: string, body: z.infer) { - const payload = FullUpdateShelterSchema.parse(body); + const payload = FullUpdateShelterSchema.parse( + await this.generatePayloadWithCoordinates(body), + ); + 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 09934e74..7a2c83f5 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 772b3e12..c8c87e0d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,5 @@ import { Logger } from '@nestjs/common'; +import { MapsApi } from '../googleMaps/mapsApi'; class ServerResponse { readonly message: string; @@ -74,6 +75,15 @@ function deepMerge(target: Record, source: Record) { return source; } } +async function fetchShelterCoordinates(address: string) { + try { + const { lat, lng } = await MapsApi.getCoordinates(address); + return { latitude: lat, longitude: lng }; + } catch (error) { + console.error(`Failed to fetch coordinates for shelter: ${error}`); + return { latitude: null, longitude: null }; + } +} export { ServerResponse, @@ -81,4 +91,5 @@ export { getSessionData, deepMerge, capitalize, + fetchShelterCoordinates, };