diff --git a/database_client/database_client.py b/database_client/database_client.py index eb4b19b5..9fa326e7 100644 --- a/database_client/database_client.py +++ b/database_client/database_client.py @@ -317,6 +317,8 @@ def get_data_sources_for_map(self) -> list[MapInfo]: INNER JOIN RECORD_TYPES RT ON RT.ID = DATA_SOURCES.RECORD_TYPE_ID WHERE DATA_SOURCES.APPROVAL_STATUS = 'approved' + AND LAT is not null + AND LNG is not null """ self.cursor.execute(sql_query) results = self.cursor.fetchall() diff --git a/middleware/common_response_formatting.py b/middleware/common_response_formatting.py index 9e16aa4e..399878cf 100644 --- a/middleware/common_response_formatting.py +++ b/middleware/common_response_formatting.py @@ -12,7 +12,7 @@ ) -def format_list_response(data: list, message: str = "") -> dict: +def format_list_response(data: dict, message: str = "") -> dict: """ Format a list of dictionaries into a dictionary with the count and data keys. Args: diff --git a/middleware/primary_resource_logic/data_sources_logic.py b/middleware/primary_resource_logic/data_sources_logic.py index 39d80d60..78f3f225 100644 --- a/middleware/primary_resource_logic/data_sources_logic.py +++ b/middleware/primary_resource_logic/data_sources_logic.py @@ -107,7 +107,9 @@ def get_data_sources_for_map_wrapper(db_client: DatabaseClient) -> Response: raw_results = db_client.get_data_sources_for_map() zipped_results = ResultFormatter.zip_get_datas_sources_for_map_results(raw_results) return make_response( - format_list_response(zipped_results), + format_list_response( + data={"data": zipped_results}, + ), HTTPStatus.OK.value, ) diff --git a/middleware/schema_and_dto_logic/primary_resource_schemas/data_sources_schemas.py b/middleware/schema_and_dto_logic/primary_resource_schemas/data_sources_schemas.py index 68b94186..dcd2ff37 100644 --- a/middleware/schema_and_dto_logic/primary_resource_schemas/data_sources_schemas.py +++ b/middleware/schema_and_dto_logic/primary_resource_schemas/data_sources_schemas.py @@ -415,3 +415,46 @@ class DataSourcesGetManyRequestSchema(GetManyRequestsBaseSchema): "default": "approved", }, ) + +class DataSourcesMapResponseInnerSchema(Schema): + data_source_id = fields.Integer( + metadata=get_json_metadata("The id of the data source") + ) + name = fields.String( + metadata=get_json_metadata("The name of the data source") + ) + agency_id = fields.Integer( + metadata=get_json_metadata("The id of the associated agency") + ) + agency_name = fields.String( + metadata=get_json_metadata("The name of the associated agency") + ) + state_iso = fields.String( + metadata=get_json_metadata("The ISO code of the state"), + ) + municipality = fields.String( + metadata=get_json_metadata("The name of the municipality"), + allow_none=True + ) + county_name = fields.String( + metadata=get_json_metadata("The name of the county"), + allow_none=True + ) + record_type = fields.String( + metadata=get_json_metadata("The type of the record") + ) + lat = fields.Float( + metadata=get_json_metadata("The latitude of the data source") + ) + lng = fields.Float( + metadata=get_json_metadata("The longitude of the data source") + ) + +class DataSourcesMapResponseSchema(MessageSchema): + data = fields.List( + fields.Nested( + DataSourcesMapResponseInnerSchema(), + metadata=get_json_metadata("The list of results"), + ), + metadata=get_json_metadata("The list of results"), + ) \ No newline at end of file diff --git a/resources/DataSources.py b/resources/DataSources.py index f9532020..582590c7 100644 --- a/resources/DataSources.py +++ b/resources/DataSources.py @@ -25,7 +25,7 @@ delete_data_source_wrapper, create_data_source_related_agency, delete_data_source_related_agency, - get_data_source_related_agencies, + get_data_source_related_agencies, get_data_sources_for_map_wrapper, ) from middleware.schema_and_dto_logic.primary_resource_schemas.data_sources_schemas import ( @@ -43,6 +43,38 @@ namespace_data_source = create_namespace(AppNamespaces.DATA_SOURCES) + # This endpoint no longer works because of the other data source endpoint + # It is interpreted as another data source id + # But we have not yet decided whether to modify or remove it entirely + + +@namespace_data_source.route("/data-sources-map") +class DataSourcesMap(PsycopgResource): + """ + A resource for managing collections of data sources for mapping. + Provides a method for retrieving all data sources. + """ + + @endpoint_info_2( + namespace=namespace_data_source, + auth_info=GET_AUTH_INFO, + schema_config=SchemaConfigs.DATA_SOURCES_MAP, + response_info=ResponseInfo( + success_message="Returns all requested data sources.", + ), + description="Retrieves location-relevant columns for data sources.", + + ) + def get(self, access_info: AccessInfo) -> Response: + """ + Retrieves location relevant columns for data sources. + + Returns: + - A dictionary containing the count of data sources and their details. + """ + return self.run_endpoint(get_data_sources_for_map_wrapper) + + @namespace_data_source.route("/") class DataSourceById(PsycopgResource): @@ -187,38 +219,6 @@ def post(self, access_info: AccessInfo) -> Response: access_info=access_info, ) - # This endpoint no longer works because of the other data source endpoint - # It is interpreted as another data source id - # But we have not yet decided whether to modify or remove it entirely - - -# @namespace_data_source.route("/data-sources-map") -# class DataSourcesMap(PsycopgResource): -# """ -# A resource for managing collections of data sources for mapping. -# Provides a method for retrieving all data sources. -# """ -# -# @handle_exceptions -# @authentication_required( -# allowed_access_methods=[AccessTypeEnum.API_KEY], -# ) -# @namespace_data_source.response(200, "Success", models.get_many_response_model) -# @namespace_data_source.response(500, "Internal server error") -# @namespace_data_source.response(400, "Bad request; missing or bad API key") -# @namespace_data_source.response(403, "Forbidden; invalid API key") -# @namespace_data_source.doc( -# description="Retrieves location-relevant columns for data sources.", -# ) -# @namespace_data_source.expect(authorization_api_parser) -# def get(self) -> Response: -# """ -# Retrieves location relevant columns for data sources. -# -# Returns: -# - A dictionary containing the count of data sources and their details. -# """ -# return self.run_endpoint(get_data_sources_for_map_wrapper) # region Related Agencies diff --git a/resources/endpoint_schema_config.py b/resources/endpoint_schema_config.py index 0253a4fd..934fcf7b 100644 --- a/resources/endpoint_schema_config.py +++ b/resources/endpoint_schema_config.py @@ -103,7 +103,7 @@ DataSourcesPostSchema, DataSourcesPutSchema, DataSourcesPostDTO, - DataSourcesGetManyRequestSchema, + DataSourcesGetManyRequestSchema, DataSourcesMapResponseSchema, ) from middleware.schema_and_dto_logic.common_response_schemas import ( IDAndMessageSchema, @@ -247,6 +247,9 @@ class SchemaConfigs(Enum): input_dto_class=DataSourcesPostDTO, primary_output_schema=IDAndMessageSchema(), ) + DATA_SOURCES_MAP = EndpointSchemaConfig( + primary_output_schema=DataSourcesMapResponseSchema(), + ) DATA_SOURCES_PUT = EndpointSchemaConfig( input_schema=DataSourcesPutSchema(), input_dto_class=EntryDataRequestSchema ) @@ -346,7 +349,7 @@ class SchemaConfigs(Enum): input_schema=GithubOAuthRequestSchema(), input_dto_class=GithubOAuthRequestDTO, ) - # endregion + #endregion REFRESH_SESSION = EndpointSchemaConfig( input_schema=RefreshSessionRequestSchema(), primary_output_schema=LoginResponseSchema(), @@ -358,3 +361,4 @@ class SchemaConfigs(Enum): primary_output_schema=MessageSchema(), input_dto_class=RequestResetPasswordRequestDTO ) + #endregion diff --git a/tests/integration/test_data_sources_map.py b/tests/integration/test_data_sources_map.py index 363d9788..a51d54d3 100644 --- a/tests/integration/test_data_sources_map.py +++ b/tests/integration/test_data_sources_map.py @@ -1,40 +1,28 @@ """Integration tests for /data-sources-map endpoint""" -from http import HTTPStatus -import psycopg -from tests.conftest import connection_with_test_data, flask_client_with_db -from tests.helper_scripts.helper_functions import ( - create_test_user_api, - create_api_key, - create_test_user_setup, -) -from tests.helper_scripts.run_and_validate_request import run_and_validate_request -from tests.helper_scripts.simple_result_validators import check_response_status +from resources.endpoint_schema_config import SchemaConfigs +from tests.helper_scripts.common_test_data import TestDataCreatorFlask + +from conftest import test_data_creator_flask, monkeysession + # This endpoint no longer works because of the other data source endpoint # It is interpreted as another data source id # But we have not yet decided whether to modify or remove it entirely -# def test_data_sources_map_get( -# flask_client_with_db, connection_with_test_data: psycopg.Connection -# ): -# """ -# Test that GET call to /data-sources-map endpoint retrieves data sources and verifies the location (latitude and longitude) of a specific source by name -# """ -# tus = create_test_user_setup(flask_client_with_db) -# response_json = run_and_validate_request( -# flask_client=flask_client_with_db, -# http_method="get", -# endpoint="/api/data-sources/data-sources-map", -# headers=tus.api_authorization_header, -# ) -# data = response_json["data"] -# found_source = False -# for result in data: -# name = result["name"] -# if name != "Source 1": -# continue -# found_source = True -# assert result["lat"] == 30 -# assert result["lng"] == 20 -# assert found_source +def test_data_sources_map_get( + test_data_creator_flask: TestDataCreatorFlask +): + """ + Test that GET call to /data-sources-map endpoint retrieves data sources and verifies the location (latitude and longitude) of a specific source by name + """ + tdc = test_data_creator_flask + tus = tdc.standard_user() + response_json = tdc.request_validator.get( + endpoint="/api/data-sources/data-sources-map", + headers=tus.api_authorization_header, + expected_schema=SchemaConfigs.DATA_SOURCES_MAP.value.primary_output_schema, + ) + data = response_json["data"] + assert len(data) > 0 +