diff --git a/api/public/public_api/api.py b/api/public/public_api/api.py index 24cdd0e6..dcf71302 100644 --- a/api/public/public_api/api.py +++ b/api/public/public_api/api.py @@ -2,6 +2,7 @@ import os from flask import Flask +from public_api.endpoints.device_location import DeviceLocationEndpoint from selene.api import SeleneResponse, selene_api from selene.api.base_config import get_base_config from selene.api.public_endpoint import check_oauth_token @@ -115,7 +116,11 @@ public.add_url_rule( view_func=WolframAlphaSpokenEndpoint.as_view('wolfram_alpha_spoken_api'), methods=['GET'] ) - +public.add_url_rule( + '/v1/device//location', + view_func=DeviceLocationEndpoint.as_view('device_location_api'), + methods=['GET'] +) """ This is a workaround to allow the API return 401 when we call a non existent path. Use case: diff --git a/api/public/public_api/endpoints/device_location.py b/api/public/public_api/endpoints/device_location.py new file mode 100644 index 00000000..09126036 --- /dev/null +++ b/api/public/public_api/endpoints/device_location.py @@ -0,0 +1,17 @@ +from http import HTTPStatus + +from selene.api import PublicEndpoint +from selene.data.device import GeographyRepository +from selene.util.db import get_db_connection + + +class DeviceLocationEndpoint(PublicEndpoint): + + def __init__(self): + super(DeviceLocationEndpoint, self).__init__() + + def get(self, device_id): + with get_db_connection(self.config['DB_CONNECTION_POOL']) as db: + location = GeographyRepository(db, None).get_location_by_device_id(device_id) + response = (location, HTTPStatus.OK) if location else ('', HTTPStatus.NOT_FOUND) + return response diff --git a/api/public/tests/features/device_location.feature b/api/public/tests/features/device_location.feature new file mode 100644 index 00000000..adaf9404 --- /dev/null +++ b/api/public/tests/features/device_location.feature @@ -0,0 +1,5 @@ +Feature: Fetch device's location + + Scenario: Location is successfully retrieved from a device + When a api call to get the location is done + Then the location should be retrieved \ No newline at end of file diff --git a/api/public/tests/features/steps/device_location.py b/api/public/tests/features/steps/device_location.py new file mode 100644 index 00000000..d2c5e0a2 --- /dev/null +++ b/api/public/tests/features/steps/device_location.py @@ -0,0 +1,47 @@ +import json +from http import HTTPStatus + +from behave import when, then +from hamcrest import assert_that, equal_to, has_key + + +@when('a api call to get the location is done') +def get_device_location(context): + login = context.device_login + device_id = login['uuid'] + access_token = login['accessToken'] + headers = dict(Authorization='Bearer {token}'.format(token=access_token)) + context.get_location_response = context.client.get( + '/v1/device/{uuid}/location'.format(uuid=device_id), + headers=headers + ) + + +@then('the location should be retrieved') +def validate_location(context): + response = context.get_location_response + assert_that(response.status_code, equal_to(HTTPStatus.OK)) + location = json.loads(response.data) + assert_that(location, has_key('coordinate')) + assert_that(location, has_key('timezone')) + assert_that(location, has_key('city')) + + coordinate = location['coordinate'] + assert_that(coordinate, has_key('latitude')) + assert_that(coordinate, has_key('longitude')) + + timezone = location['timezone'] + assert_that(timezone, has_key('name')) + assert_that(timezone, has_key('offset')) + assert_that(timezone, has_key('dstOffset')) + + city = location['city'] + assert_that(city, has_key('name')) + assert_that(city, has_key('state')) + + state = city['state'] + assert_that(state, has_key('name')) + assert_that(state, has_key('country')) + + country = state['country'] + assert_that(country, has_key('name')) diff --git a/shared/selene/data/device/repository/geography.py b/shared/selene/data/device/repository/geography.py index 5b44c788..58b7c4ee 100644 --- a/shared/selene/data/device/repository/geography.py +++ b/shared/selene/data/device/repository/geography.py @@ -46,3 +46,10 @@ class GeographyRepository(RepositoryBase): db_result = self.cursor.insert_returning(db_request) return db_result['id'] + + def get_location_by_device_id(self, device_id): + db_request = self._build_db_request( + sql_file_name='get_location_by_device_id.sql', + args=dict(device_id=device_id) + ) + return self.cursor.select_one(db_request) diff --git a/shared/selene/data/device/repository/sql/get_location_by_device_id.sql b/shared/selene/data/device/repository/sql/get_location_by_device_id.sql new file mode 100644 index 00000000..0062cc4a --- /dev/null +++ b/shared/selene/data/device/repository/sql/get_location_by_device_id.sql @@ -0,0 +1,33 @@ +SELECT + json_build_object( + 'latitude', city.latitude, + 'longitude', city.longitude + ) as coordinate, + json_build_object( + 'name', timezone.name, + 'offset', timezone.gmt_offset, + 'dstOffset', timezone.dst_offset + ) as timezone, + json_build_object( + 'name', city.name, + 'state', json_build_object( + 'name', region.name, + 'country', json_build_object( + 'name', country.name + ) + ) + ) as city +FROM + device.device dev +INNER JOIN + device.geography geo ON dev.geography_id = geo.id +INNER JOIN + geography.country country ON geo.country_id = country.id +INNER JOIN + geography.region region ON geo.region_id = region.id +INNER JOIN + geography.city city ON geo.city_id = city.id +INNER JOIN + geography.timezone timezone ON geo.timezone_id = timezone.id +WHERE + dev.id = %(device_id)s \ No newline at end of file