Skip to content

Commit

Permalink
fix: add cors to FAST API (#337)
Browse files Browse the repository at this point in the history
* fix: add cors

* fix: missing api base prefix

* fix: hardcoded word. supposed to be place holder

* fix: wrong url in collection items as well. refactor

* fix: add self link + single granule endpoint

* fix: some error

* fix: add parent link
  • Loading branch information
wphyojpl authored Mar 11, 2024
1 parent 4c9a938 commit 22f35d8
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,8 @@ def to_stac(self, source: dict) -> dict:

}, 'root')] + \
[self.__convert_to_stac_link_obj(k) for k in source['files']] + \
[Link(rel='items', target=f'{self.__items_base_url}/{WebServiceConstants.COLLECTIONS}/{collection_id}/items', media_type='application/json', title=f"{collection_id} Granules")]
[Link(rel='parent', target=f'{self.__items_base_url}/{WebServiceConstants.CATALOG}', media_type='application/json', title=f"UDS Catalog"),
Link(rel='items', target=f'{self.__items_base_url}/{WebServiceConstants.COLLECTIONS}/{collection_id}/items', media_type='application/json', title=f"{collection_id} Granules")]
return stac_collection.to_dict(include_self_link=False, transform_hrefs=False)

def get_href(self, input_href: str):
Expand Down
9 changes: 2 additions & 7 deletions cumulus_lambda_functions/uds_api/catalog_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
import os
from typing import Union

Expand All @@ -16,14 +15,9 @@

from cumulus_lambda_functions.lib.lambda_logger_generator import LambdaLoggerGenerator
from fastapi import APIRouter, HTTPException, Request, Response

from cumulus_lambda_functions.uds_api.dapa.collections_dapa_cnm import CnmRequestBody, CollectionsDapaCnm
from cumulus_lambda_functions.uds_api.dapa.collections_dapa_creation import CollectionDapaCreation, \
CumulusCollectionModel
from cumulus_lambda_functions.uds_api.dapa.collections_dapa_query import CollectionDapaQuery
from cumulus_lambda_functions.uds_api.dapa.pagination_links_generator import PaginationLinksGenerator
from cumulus_lambda_functions.uds_api.web_service_constants import WebServiceConstants
from fastapi.responses import PlainTextResponse, JSONResponse

LOGGER = LambdaLoggerGenerator.get_logger(__name__, LambdaLoggerGenerator.get_level_from_env())

Expand Down Expand Up @@ -70,9 +64,10 @@ async def get_catalog(request: Request, limit: Union[int, None] = 10, offset: Un
title='Unity DS Catalog',
href=pg_link_generator.base_url
)
api_base_prefix = FastApiUtils.get_api_base_prefix()
authorized_collections_links = [Link(
rel='child',
target=f'{pg_link_generator.base_url}/collections/{k["collection_id"]}',
target=f'{pg_link_generator.base_url}/{api_base_prefix}/collections/{k["collection_id"]}',
media_type='application/json',
title=k["collection_id"],
) for k in authorized_collections]
Expand Down
6 changes: 4 additions & 2 deletions cumulus_lambda_functions/uds_api/collections_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,9 @@ async def get_single_collection(request: Request, collection_id: str, limit: Uni
custom_params['limit'] = limit
LOGGER.debug(f'new limit: {limit}')
pg_link_generator = PaginationLinksGenerator(request, custom_params)
api_base_prefix = FastApiUtils.get_api_base_prefix()
collections_dapa_query = CollectionDapaQuery(collection_id, limit, offset, None,
pg_link_generator.base_url)
f'{pg_link_generator.base_url}/{api_base_prefix}')
collections_result = collections_dapa_query.get_collection()
except Exception as e:
LOGGER.exception('failed during get_granules_dapa')
Expand Down Expand Up @@ -265,7 +266,8 @@ async def query_collections(request: Request, collection_id: Union[str, None] =
LOGGER.debug(f'new limit: {limit}')
pg_link_generator = PaginationLinksGenerator(request, custom_params)
pagination_links = pg_link_generator.generate_pagination_links()
collections_dapa_query = CollectionDapaQuery(collection_id, limit, offset, pagination_links, pg_link_generator.base_url)
api_base_prefix = FastApiUtils.get_api_base_prefix()
collections_dapa_query = CollectionDapaQuery(collection_id, limit, offset, pagination_links, f'{pg_link_generator.base_url}/{api_base_prefix}')
collections_result = collections_dapa_query.start()
except Exception as e:
LOGGER.exception('failed during get_granules_dapa')
Expand Down
32 changes: 31 additions & 1 deletion cumulus_lambda_functions/uds_api/dapa/granules_dapa_query_es.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
from copy import deepcopy

from pystac import Link

from cumulus_lambda_functions.uds_api.dapa.pagination_links_generator import PaginationLinksGenerator
from cumulus_lambda_functions.lib.aws.es_middleware import ESMiddleware
from cumulus_lambda_functions.lib.cql_parser import CqlParser
from cumulus_lambda_functions.lib.uds_db.uds_collections import UdsCollections
from cumulus_lambda_functions.lib.lambda_logger_generator import LambdaLoggerGenerator
from cumulus_lambda_functions.uds_api.dapa.granules_db_index import GranulesDbIndex
from cumulus_lambda_functions.uds_api.web_service_constants import WebServiceConstants

LOGGER = LambdaLoggerGenerator.get_logger(__name__, LambdaLoggerGenerator.get_level_from_env())


class GranulesDapaQueryEs:
def __init__(self, collection_id, limit, offset, input_datetime, filter_input, pagination_link_obj: PaginationLinksGenerator):
def __init__(self, collection_id, limit, offset, input_datetime, filter_input, pagination_link_obj: PaginationLinksGenerator, base_url):
self.__pagination_link_obj = pagination_link_obj
self.__input_datetime = input_datetime
self.__collection_id = collection_id
self.__limit = limit
self.__offset = offset
self.__base_url = base_url
self.__filter_input = filter_input
self.__granules_index = GranulesDbIndex()

Expand Down Expand Up @@ -81,6 +85,30 @@ def __create_pagination_links(self, page_marker_str):
pagination_links.append({'rel': 'next', 'href': f"{self.__pagination_link_obj.requesting_base_url}?{'&'.join([f'{k}={v}' for k, v in new_queries.items()])}"})
return pagination_links

def get_single_granule(self, granule_id):
granules_query_dsl = {
'query': {'bool': {'must': [{
'term': {'id': granule_id}
}]}}
}
LOGGER.debug(f'granules_query_dsl: {granules_query_dsl}')
collection_identifier = UdsCollections.decode_identifier(self.__collection_id)
granules_query_result = GranulesDbIndex().dsl_search(collection_identifier.tenant,
collection_identifier.venue,
granules_query_dsl)
LOGGER.debug(f'granules_query_result: {granules_query_result}')
if len(granules_query_result['hits']['hits']) < 1:
raise ValueError(f'cannot find granule for : {granule_id}')

each_granules_query_result_stripped = granules_query_result['hits']['hits'][0]['_source']
self_link = Link(rel='self', target=f'{self.__base_url}/{WebServiceConstants.COLLECTIONS}/{self.__collection_id}/items/{each_granules_query_result_stripped["id"]}', media_type='application/json', title=each_granules_query_result_stripped["id"]).to_dict(False)
each_granules_query_result_stripped['links'].append(self_link)
if 'event_time' in each_granules_query_result_stripped:
each_granules_query_result_stripped.pop('event_time')
if 'bbox' in each_granules_query_result_stripped:
each_granules_query_result_stripped['bbox'] = GranulesDbIndex.from_es_bbox(each_granules_query_result_stripped['bbox'])
return each_granules_query_result_stripped

def start(self):
try:
granules_query_dsl = self.__generate_es_dsl()
Expand All @@ -93,6 +121,8 @@ def start(self):
result_size = ESMiddleware.get_result_size(granules_query_result)
granules_query_result_stripped = [k['_source'] for k in granules_query_result['hits']['hits']]
for each_granules_query_result_stripped in granules_query_result_stripped:
self_link = Link(rel='self', target=f'{self.__base_url}/{WebServiceConstants.COLLECTIONS}/{self.__collection_id}/items/{each_granules_query_result_stripped["id"]}', media_type='application/json', title=each_granules_query_result_stripped["id"]).to_dict(False)
each_granules_query_result_stripped['links'].append(self_link)
if 'event_time' in each_granules_query_result_stripped:
each_granules_query_result_stripped.pop('event_time')
if 'bbox' in each_granules_query_result_stripped:
Expand Down
9 changes: 9 additions & 0 deletions cumulus_lambda_functions/uds_api/fast_api_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import base64
import json
import os

from cumulus_lambda_functions.lib.constants import Constants

from cumulus_lambda_functions.lib.lambda_logger_generator import LambdaLoggerGenerator
from fastapi import APIRouter, HTTPException, Request, Response

from cumulus_lambda_functions.uds_api.web_service_constants import WebServiceConstants

LOGGER = LambdaLoggerGenerator.get_logger(__name__, LambdaLoggerGenerator.get_level_from_env())


Expand All @@ -29,3 +34,7 @@ def get_authorization_info(request: Request):
'action': action,
'resource': resource,
}

@staticmethod
def get_api_base_prefix():
return os.environ.get(Constants.DAPA_API_PREIFX_KEY) if Constants.DAPA_API_PREIFX_KEY in os.environ else WebServiceConstants.API_PREFIX
34 changes: 31 additions & 3 deletions cumulus_lambda_functions/uds_api/granules_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,42 @@ async def get_granules_dapa(request: Request, collection_id: str, limit: Union[i
}))

try:
# pagination_links = PaginationLinksGenerator(request).generate_pagination_links()
pagination_links = PaginationLinksGenerator(request)

granules_dapa_query = GranulesDapaQueryEs(collection_id, limit, offset, datetime, filter, pagination_links)
api_base_prefix = FastApiUtils.get_api_base_prefix()
granules_dapa_query = GranulesDapaQueryEs(collection_id, limit, offset, datetime, filter, pagination_links, f'{pagination_links.base_url}/{api_base_prefix}')
granules_result = granules_dapa_query.start()
except Exception as e:
LOGGER.exception('failed during get_granules_dapa')
raise HTTPException(status_code=500, detail=str(e))
if granules_result['statusCode'] == 200:
return granules_result['body']
raise HTTPException(status_code=granules_result['statusCode'], detail=granules_result['body'])


@router.get("/{collection_id}/items/{granule_id}")
@router.get("/{collection_id}/items/{granule_id}/")
async def get_single_granule_dapa(request: Request, collection_id: str, granule_id: str):
authorizer: UDSAuthorizorAbstract = UDSAuthorizerFactory() \
.get_instance(UDSAuthorizerFactory.cognito,
es_url=os.getenv('ES_URL'),
es_port=int(os.getenv('ES_PORT', '443'))
)
auth_info = FastApiUtils.get_authorization_info(request)
collection_identifier = UdsCollections.decode_identifier(collection_id)
if not authorizer.is_authorized_for_collection(DBConstants.read, collection_id,
auth_info['ldap_groups'],
collection_identifier.tenant,
collection_identifier.venue):
LOGGER.debug(f'user: {auth_info["username"]} is not authorized for {collection_id}')
raise HTTPException(status_code=403, detail=json.dumps({
'message': 'not authorized to execute this action'
}))
try:
api_base_prefix = FastApiUtils.get_api_base_prefix()
pg_link_generator = PaginationLinksGenerator(request)
granules_dapa_query = GranulesDapaQueryEs(collection_id, 1, None, None, filter, None, f'{pg_link_generator.base_url}/{api_base_prefix}')
granules_result = granules_dapa_query.get_single_granule(granule_id)
except Exception as e:
LOGGER.exception('failed during get_granules_dapa')
raise HTTPException(status_code=500, detail=str(e))
return granules_result
38 changes: 30 additions & 8 deletions cumulus_lambda_functions/uds_api/web_service.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
import os

from fastapi.openapi.utils import get_openapi

from cumulus_lambda_functions.lib.constants import Constants

from cumulus_lambda_functions.uds_api.fast_api_utils import FastApiUtils
from cumulus_lambda_functions.lib.lambda_logger_generator import LambdaLoggerGenerator
from dotenv import load_dotenv

load_dotenv()

import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from mangum import Mangum
from starlette.requests import Request

from cumulus_lambda_functions.uds_api.routes_api import main_router
from cumulus_lambda_functions.uds_api.web_service_constants import WebServiceConstants
LOGGER = LambdaLoggerGenerator.get_logger(__name__, LambdaLoggerGenerator.get_level_from_env())

api_base_prefix = os.environ.get(Constants.DAPA_API_PREIFX_KEY) if Constants.DAPA_API_PREIFX_KEY in os.environ else WebServiceConstants.API_PREFIX
api_base_prefix = FastApiUtils.get_api_base_prefix()
app = FastAPI(title='Unity UDS API',
description='API to interact with UDS services',
docs_url=f'/{api_base_prefix}/docs',
redoc_url=f'/{api_base_prefix}/redoc',
openapi_url=f'/{api_base_prefix}/docs/openapi',
)
origins = [
"http://localhost",
"http://localhost:8080",
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(main_router, prefix=f'/{api_base_prefix}')
"""
Accept-Ranges:
bytes
Access-Control-Allow-Methods:
HEAD, GET
Access-Control-Allow-Origin:
*
Access-Control-Expose-Headers:
ETag, x-amz-meta-custom-header
Access-Control-Max-Age:
3000
"""

# https://fastapi.tiangolo.com/tutorial/cors/

@app.get("/")
async def root(request: Request):
Expand Down
17 changes: 16 additions & 1 deletion tests/integration_tests/test_uds_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def test_collections_get_single_granule(self):
"""
{'type': 'Collection', 'id': 'URN:NASA:UNITY:UDS_LOCAL_TEST:DEV:UDS_COLLECTION___2402011700', 'stac_version': '1.0.0', 'description': 'TODO', 'links': [{'rel': 'root', 'href': './collection.json?bucket=unknown_bucket&regex=%5Eabcd.1234.efgh.test_file.%2A%5C.data.stac.json%24', 'type': 'application/json', 'title': 'abcd.1234.efgh.test_file05.data.stac.json'}, {'rel': 'item', 'href': './collection.json?bucket=protected&regex=%5Eabcd.1234.efgh.test_file.%2A%5C.nc%24', 'type': 'data', 'title': 'abcd.1234.efgh.test_file05.nc'}, {'rel': 'item', 'href': './collection.json?bucket=protected&regex=%5Eabcd.1234.efgh.test_file.%2A%5C.nc.cas%24', 'type': 'metadata', 'title': 'abcd.1234.efgh.test_file05.nc.cas'}, {'rel': 'item', 'href': './collection.json?bucket=protected&regex=%5Eabcd.1234.efgh.test_file.%2A%5C.nc.cmr.xml%24', 'type': 'metadata', 'title': 'abcd.1234.efgh.test_file05.nc.cmr.xml'}, {'rel': 'item', 'href': './collection.json?bucket=protected&regex=%5Eabcd.1234.efgh.test_file.%2A%5C.nc.stac.json%24', 'type': 'metadata', 'title': 'abcd.1234.efgh.test_file05.nc.stac.json'}, {'rel': 'items', 'href': 'https://dxebrgu0bc9w7.cloudfront.net/collections/URN:NASA:UNITY:UDS_LOCAL_TEST:DEV:UDS_COLLECTION___2402011700/items', 'type': 'application/json', 'title': 'URN:NASA:UNITY:UDS_LOCAL_TEST:DEV:UDS_COLLECTION___2402011700 Granules'}], 'extent': {'spatial': {'bbox': [[0.0, 0.0, 0.0, 0.0]]}, 'temporal': {'interval': [['1970-01-01T12:00:00Z', '2024-02-26T07:11:11Z']]}}, 'license': 'proprietary', 'summaries': {'updated': ['2024-02-01T17:55:34.338000Z'], 'granuleId': ['^abcd.1234.efgh.test_file.*$'], 'granuleIdExtraction': ['(^abcd.1234.efgh.test_file.*)(\\.data\\.stac\\.json|\\.nc\\.cas|\\.cmr\\.xml)'], 'process': ['stac'], 'totalGranules': [1]}}
"""
print(query_result)
print(json.dumps(query_result, indent=4))
self.assertEqual('Collection', query_result['type'], f'wrong type: {query_result}')
self.assertEqual(temp_collection_id, query_result['id'], f'wrong collection_id: {query_result}')
self.assertTrue('links' in query_result, 'links missing')
Expand All @@ -201,6 +201,21 @@ def test_granules_get(self):
self.assertTrue(v.startswith(self.uds_url), f'missing stage: {self.stage} in {v} for {k}')
return

def test_single_granule_get(self):
post_url = f'{self.uds_url}collections/URN:NASA:UNITY:UDS_LOCAL_TEST:DEV:UDS_COLLECTION___2312041030/items/URN:NASA:UNITY:UDS_LOCAL_TEST:DEV:UDS_COLLECTION___2312041030:test_file01'
headers = {
'Authorization': f'Bearer {self.bearer_token}',
}
print(post_url)
query_result = requests.get(url=post_url,
headers=headers,
)
self.assertEqual(query_result.status_code, 200, f'wrong status code. {query_result.text}')
response_json = json.loads(query_result.text)
print(json.dumps(response_json))

return

def test_create_new_collection(self):
post_url = f'{self.uds_url}collections/' # MCP Dev
headers = {
Expand Down

0 comments on commit 22f35d8

Please sign in to comment.