import { ALBHandler, ALBEvent, ALBEventHeaders, ALBResult } from 'aws-lambda'
import countries from 'i18n-iso-countries'
import {
LocationClient,
SearchPlaceIndexForTextCommand,
} from '@aws-sdk/client-location'
interface FullAddress {
name: string
zip: string
city: string
cityDistrict: string
country: string
countryCode: string
administrativeArea: string
streetName: string
houseNumber: string
}
function getTenant(headers: ALBEventHeaders = {}): string | undefined {
try {
if (headers['X-Newstore-Tenant'] !== undefined) {
return headers['X-Newstore-Tenant']
}
if (headers['x-newstore-tenant'] !== undefined) {
return headers['x-newstore-tenant']
}
const authToken =
headers.Authorization?.split(' ')[1] ||
headers.authorization?.split(' ')[1]
if (typeof authToken === 'string') {
const [, rawToken] = authToken.split('.')
const buff = Buffer.from(rawToken, 'base64')
const tokenData = buff.toString('utf-8')
const data = JSON.parse(tokenData)
if (data?.['http://newstore/tenant'] !== undefined) {
return data['http://newstore/tenant']
}
}
} catch (e) {
}
}
function formatJsonResponse (statusCode: number, body?: unknown): ALBResult {
return {
statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}
}
const completeWithAWS = async (
query: string,
context: string,
locale = 'en-us',
): Promise<FullAddress | null> => {
const language = locale.split('-')[0]
const client = new LocationClient({
region: process.env.REGION ?? 'us-east-1',
})
const command = new SearchPlaceIndexForTextCommand({
IndexName: 'address-completion',
Text: query + ' ' + context,
FilterCategories: ['AddressType', 'StreetType'],
Language: language,
})
const response = await client.send(command)
if (response.Results === undefined || response.Results.length === 0)
return null
const firstResult = response.Results[0]
if (firstResult.Place === undefined) return null
return {
name: query,
zip: firstResult.Place.PostalCode ?? '',
city: firstResult.Place.Municipality ?? '',
cityDistrict: firstResult.Place.Neighborhood ?? '',
country:
(firstResult.Place.Country &&
countries.getName(firstResult.Place.Country, language, {
select: 'official',
})) ||
'',
countryCode:
(firstResult.Place.Country &&
countries.alpha3ToAlpha2(firstResult.Place.Country)) ||
'',
administrativeArea: firstResult.Place.Region ?? '',
streetName: firstResult.Place.Street ?? '',
houseNumber: firstResult.Place.AddressNumber ?? '',
}
}
export const handler: ALBHandler = async (event: ALBEvent) => {
const tenant = getTenant(event.headers)
if (typeof tenant !== 'string') {
return formatJsonResponse(400, { error: 'Missing tenant header.' })
}
try {
const query = event.queryStringParameters?.query
const context = event.queryStringParameters?.context
const language = event.queryStringParameters?.lang
if (typeof query !== 'string') {
return formatJsonResponse(400, { error: 'Missing query parameter.' })
}
if (typeof context !== 'string') {
return formatJsonResponse(400, { error: 'Missing context parameter.' })
}
const address = await completeWithAWS(query, context, language)
if (address === null) return formatJsonResponse(404)
return formatJsonResponse(200, address)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return formatJsonResponse(400, { error: message })
}
}