diff --git a/pages/api/geolocation/v1/index.js b/pages/api/geolocation/v1/index.js new file mode 100644 index 00000000..6859c3af --- /dev/null +++ b/pages/api/geolocation/v1/index.js @@ -0,0 +1,57 @@ +import app from '@/app'; +import BadRequestError from '@/errors/BadRequestError'; + +import axios from 'axios'; + +function validateGeolocation(latitude = '', longitude = '') { + const latitudeRegex = new RegExp( + /^(-?[1-8]?\d(?:\.\d{1,18})?|90(?:\.0{1,18})?)$/ + ); + const longitudeRegex = new RegExp( + /^(-?(?:1[0-7]|[1-9])?\d(?:\.\d{1,18})?|180(?:\.0{1,18})?)$/ + ); + + if (latitude.match(latitudeRegex) && longitude.match(longitudeRegex)) { + return true; + } + + return false; +} + +async function geolocation(request, response) { + const defaultLanguage = 'pt'; + const { latitude, longitude, language } = request.query; + + const isValidGeolocation = validateGeolocation(latitude, longitude); + + if (!isValidGeolocation) { + throw new BadRequestError({ + message: 'Geolocalização inválida', + }); + } + + try { + const result = await axios.get( + ` + https://api.bigdatacloud.net/data/reverse-geocode-client + `, + { + params: { + latitude, + longitude, + localityLanguage: language || defaultLanguage, + }, + } + ); + + const { city, countryName } = result.data; + + return response.json({ city, country: countryName }); + } catch (error) { + throw new InternalError({ + message: 'Erro ao buscar informações sobre a geolocalização', + }); + } +} + +export default app().get(geolocation); diff --git a/pages/docs/doc/geolocation.json b/pages/docs/doc/geolocation.json new file mode 100644 index 00000000..5617068b --- /dev/null +++ b/pages/docs/doc/geolocation.json @@ -0,0 +1,125 @@ +{ + "tags": [ + { + "name": "Geolocalização", + "description": "Informações relacionadas à geolocalização" + } + ], + "paths": { + "/geolocation/v1": { + "get": { + "tags": [ + "Geolocalização" + ], + "summary": "Retorna cidade e país pela latitude e longitude", + "description": "", + "parameters": [ + { + "name": "latitude", + "description": "Latitude geográfica. Um número decimal que representa a coordenada norte-sul.", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "longitude", + "description": "Longitude geográfica. Um número decimal que representa a coordenada leste-oeste.", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeolocationInfo" + } + } + } + }, + "400": { + "description": "Geolocalização inválida", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + }, + "example": { + "name": "BadRequestError", + "message": "Geolocalização inválida", + "type": "bad_request" + } + } + } + }, + "500": { + "description": "Erro ao buscar informações sobre a geolocalização", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + }, + "example": { + "name": "InternalError", + "message": "Erro ao buscar informações sobre a geolocalização", + "type": "internal" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "GeolocationInfo": { + "title": "Geolocation Info", + "required": [ + "city", + "country" + ], + "type": "object", + "properties": { + "city": { + "type": "string" + }, + "country": { + "type": "string" + } + }, + "example": { + "city": "São Paulo", + "country": "Brasil" + } + }, + "ErrorMessage": { + "title": "Error Message", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "message": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "example": { + "name": "InternalError", + "message": "Erro ao buscar informações sobre a geolocalização", + "type": "internal" + } + } + } + } +} diff --git a/tests/geolocation-v1.test.js b/tests/geolocation-v1.test.js new file mode 100644 index 00000000..2c8523dd --- /dev/null +++ b/tests/geolocation-v1.test.js @@ -0,0 +1,29 @@ +const axios = require('axios'); + +describe('/geolocation/v1 (E2E)', () => { + test('it should be return the city and country if the coordinates exists', async () => { + const requestUrl = `${global.SERVER_URL}/api/geolocation/v1?latitude=-22.871304&longitude=-47.2044911&language=pt`; + const response = await axios.get(requestUrl); + + expect(response.data).toEqual({ + city: 'Região Metropolitana de Campinas', + country: 'Brasil', + }); + }); + + test('it should be return an error if coordinates format is invalid', async () => { + const requestUrl = `${global.SERVER_URL}/api/geolocation/v1?latitude=-0&longitude=0`; + + try { + await axios.get(requestUrl); + } catch (error) { + const { response } = error; + expect(response.status).toBe(400); + expect(response.data).toBe({ + message: 'Geolocalização inválida', + name: 'BadRequestError', + type: 'bad_request', + }); + } + }); +});