fix(webapp): catch possibe errors on request geolocation (#8640)

* catch possibe errors on request geolocation

* proper toast error

* remove deprecated request package, use node fetch instead, set timeout

---------

Co-authored-by: Max <maxharz@gmail.com>
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
This commit is contained in:
Moriz Wahl 2025-06-25 19:45:46 +02:00 committed by GitHub
parent a8de785783
commit 4eff0fb497
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 99 additions and 88 deletions

View File

@ -83,7 +83,6 @@
"nodemailer-html-to-text": "^3.2.0", "nodemailer-html-to-text": "^3.2.0",
"preview-email": "^3.1.0", "preview-email": "^3.1.0",
"pug": "^3.0.3", "pug": "^3.0.3",
"request": "~2.88.2",
"sanitize-html": "~2.17.0", "sanitize-html": "~2.17.0",
"slug": "~9.1.0", "slug": "~9.1.0",
"trunc-html": "~1.1.2", "trunc-html": "~1.1.2",

View File

@ -6,27 +6,15 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable promise/avoid-new */ /* eslint-disable n/no-unsupported-features/node-builtins */
/* eslint-disable promise/prefer-await-to-callbacks */
import { UserInputError } from 'apollo-server' import { UserInputError } from 'apollo-server'
import request from 'request'
import CONFIG from '@config/index' import CONFIG from '@config/index'
const fetch = (url) => {
return new Promise((resolve, reject) => {
request(url, function (error, response, body) {
if (error) {
reject(error)
} else {
resolve(JSON.parse(body))
}
})
})
}
const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl', 'ru'] const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl', 'ru']
const REQUEST_TIMEOUT = 3000
const createLocation = async (session, mapboxData) => { const createLocation = async (session, mapboxData) => {
const data = { const data = {
id: mapboxData.id + (mapboxData.address ? `-${mapboxData.address}` : ''), id: mapboxData.id + (mapboxData.address ? `-${mapboxData.address}` : ''),
@ -78,74 +66,80 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s
let locationId let locationId
if (locationName !== null) { try {
const res: any = await fetch( if (locationName !== null) {
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent( const response: any = await fetch(
locationName, `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
)}.json?access_token=${ locationName,
CONFIG.MAPBOX_TOKEN )}.json?access_token=${
}&types=region,place,country,address&language=${locales.join(',')}`, CONFIG.MAPBOX_TOKEN
) }&types=region,place,country,address&language=${locales.join(',')}`,
{
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
},
)
if (!res?.features?.[0]) { const res = await response.json()
throw new UserInputError('locationName is invalid')
}
let data if (!res?.features?.[0]) {
throw new UserInputError('locationName is invalid')
res.features.forEach((item) => {
if (item.matching_place_name === locationName) {
data = item
} }
})
if (!data) {
data = res.features[0]
}
if (!data?.place_type?.length) { let data
throw new UserInputError('locationName is invalid')
}
if (data.place_type.length > 1) { res.features.forEach((item) => {
data.id = 'region.' + data.id.split('.')[1] if (item.matching_place_name === locationName) {
} data = item
await createLocation(session, data) }
})
if (!data) {
data = res.features[0]
}
let parent = data if (!data?.place_type?.length) {
throw new UserInputError('locationName is invalid')
}
if (parent.address) { if (data.place_type.length > 1) {
parent.id += `-${parent.address}` data.id = 'region.' + data.id.split('.')[1]
} }
await createLocation(session, data)
if (data.context) { let parent = data
for await (const ctx of data.context) {
await createLocation(session, ctx) if (parent.address) {
await session.writeTransaction((transaction) => { parent.id += `-${parent.address}`
return transaction.run( }
`
if (data.context) {
for await (const ctx of data.context) {
await createLocation(session, ctx)
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
MERGE (child)<-[:IS_IN]-(parent) MERGE (child)<-[:IS_IN]-(parent)
RETURN child.id, parent.id RETURN child.id, parent.id
`, `,
{ {
parentId: parent.id, parentId: parent.id,
childId: ctx.id, childId: ctx.id,
}, },
) )
}) })
parent = ctx parent = ctx
}
} }
locationId = data.id
} else {
locationId = 'non-existent-id'
} }
locationId = data.id // delete all current locations from node and add new location
} else { await session.writeTransaction((transaction) => {
locationId = 'non-existent-id' return transaction.run(
} `
// delete all current locations from node and add new location
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (node:${nodeLabel} {id: $nodeId}) MATCH (node:${nodeLabel} {id: $nodeId})
OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location) OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location)
DELETE relationship DELETE relationship
@ -154,18 +148,29 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s
MERGE (node)-[:IS_IN]->(location) MERGE (node)-[:IS_IN]->(location)
RETURN location.id, node.id RETURN location.id, node.id
`, `,
{ nodeId, locationId }, { nodeId, locationId },
) )
}) })
} catch (error) {
throw new Error(error)
}
} }
export const queryLocations = async ({ place, lang }) => { export const queryLocations = async ({ place, lang }) => {
const res: any = await fetch( try {
`https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`, const res: any = await fetch(
) `https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`,
// Return empty array if no location found or error occurred {
if (!res?.features) { signal: AbortSignal.timeout(REQUEST_TIMEOUT),
return [] },
)
const response = await res.json()
// Return empty array if no location found or error occurred
if (!response?.features) {
return []
}
return response.features
} catch (error) {
throw new Error(error)
} }
return res.features
} }

View File

@ -107,19 +107,26 @@ export default {
this.cities = [] this.cities = []
return return
} }
this.loadingGeo = true
const place = encodeURIComponent(value) try {
const lang = this.$i18n.locale() this.loadingGeo = true
const { const place = encodeURIComponent(value)
data: { queryLocations: result }, const lang = this.$i18n.locale()
} = await this.$apollo.query({ query: queryLocations(), variables: { place, lang } })
this.cities = this.processLocationsResult(result) const {
this.loadingGeo = false data: { queryLocations: result },
} = await this.$apollo.query({ query: queryLocations(), variables: { place, lang } })
return this.cities.find((city) => city.value === value) this.cities = this.processLocationsResult(result)
this.loadingGeo = false
return this.cities.find((city) => city.value === value)
} catch (error) {
this.$toast.error(error.message)
} finally {
this.loadingGeo = false
}
}, },
clearLocationName(event) { clearLocationName(event) {
event.target.value = '' event.target.value = ''