diff --git a/biocompute/apis.py b/biocompute/apis.py index 8f7417f..13acc89 100644 --- a/biocompute/apis.py +++ b/biocompute/apis.py @@ -4,7 +4,7 @@ """BioCompute Object APIs """ -from authentication.services import CustomJSONWebTokenAuthentication +import jsonref from biocompute.services import ( BcoDraftSerializer, BcoValidator, @@ -13,13 +13,16 @@ bco_counter_increment ) from biocompute.selectors import ( + object_id_deconstructor, retrieve_bco, user_can_modify_bco, - user_can_publish_bco, + user_can_publish_draft, + user_can_publish_prefix, + prefix_from_object_id, ) from config.services import ( legacy_api_converter, - response_constructor, + bulk_response_constructor, response_status, ) from drf_yasg import openapi @@ -34,6 +37,7 @@ from tests.fixtures.testing_bcos import BCO_000001_DRAFT hostname = settings.PUBLIC_HOSTNAME +BASE_DIR = settings.BASE_DIR BCO_DRAFT_SCHEMA = openapi.Schema( type=openapi.TYPE_ARRAY, @@ -114,7 +118,7 @@ def post(self, request) -> Response: return Response( status=status.HTTP_200_OK, data=[ - response_constructor( + bulk_response_constructor( identifier=test_object_id, status = "SUCCESS", code= 200, @@ -129,7 +133,7 @@ def post(self, request) -> Response: prefix_permitted = user_can_draft_prefix(owner, bco_prefix) if prefix_permitted is None: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "NOT FOUND", code= 404, @@ -139,7 +143,7 @@ def post(self, request) -> Response: continue if prefix_permitted is False: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "FORBIDDEN", code= 400, @@ -155,7 +159,7 @@ def post(self, request) -> Response: bco_instance = serialized_bco.create(serialized_bco.validated_data) response_id = bco_instance.object_id score = bco_instance.score - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SUCCESS", code= 200, @@ -164,7 +168,7 @@ def post(self, request) -> Response: accepted_requests = True except Exception as err: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=serialized_bco['object_id'].value, status = "SERVER ERROR", code= 500, @@ -172,7 +176,7 @@ def post(self, request) -> Response: )) else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "REJECTED", code= 400, @@ -184,6 +188,149 @@ def post(self, request) -> Response: status_code = response_status(accepted_requests, rejected_requests) return Response(status=status_code, data=response_data) +class PublishBcoApi(APIView): + """Publish Single BCO + + API endpoint for publishing a BioCompute Object (BCO). + + This endpoint allows authenticated users to publish an individual BCO. + The draft is validated and processed upon submission. + """ + + permission_classes = [IsAuthenticated] + # swagger_schema = None + #TODO: Add Swaggar docs + # schema = jsonref.load_uri( + # f"file://{BASE_DIR}/config/IEEE/2791object.json" + # ) + @swagger_auto_schema( + operation_id="api_objects_publish", + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + # properties=schema, + + ), + responses={ + 200: "Request was accepted.", + 400: "Requests was rejected.", + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) + + def post(self, request) -> Response: + validator = BcoValidator() + requester = request.user + data = request.data + identifier, bco_results = validator.parse_and_validate(data).popitem() + prefix_name = prefix_from_object_id(identifier) + publish_permission = user_can_publish_prefix(requester, prefix_name) + + if bco_results["number_of_errors"] > 0: + response_data = bulk_response_constructor( + identifier = identifier, + status="FAILED", + code=400, + message="BCO not valid", + data=bco_results + ) + else: + response_data = bulk_response_constructor( + identifier = identifier, + status="SUCCESS", + code=200, + message="BCO valid", + data=bco_results + ) + + import pdb; pdb.set_trace() + return Response(status=status.HTTP_200_OK, data=response_data) + + # for index, object in enumerate(data): + # response_id = object.get("object_id", index) + # bco_instance = user_can_publish_draft(object, requester) + + # if bco_instance is None: + # response_data.append(bulk_response_constructor( + # identifier=response_id, + # status = "NOT FOUND", + # code= 404, + # message= f"Invalid BCO: {response_id} does not exist.", + # )) + # rejected_requests = True + # continue + + # if bco_instance is False: + # response_data.append(bulk_response_constructor( + # identifier=response_id, + # status = "FORBIDDEN", + # code= 403, + # message= f"User, {requester}, does not have draft permissions"\ + # + f" for BCO {response_id}.", + # )) + # rejected_requests = True + # continue + + # if type(bco_instance) is str: + # response_data.append(bulk_response_constructor( + # identifier=response_id, + # status = "BAD REQUEST", + # code= 400, + # message= bco_instance + # )) + # rejected_requests = True + # continue + + # if type(bco_instance) is tuple: + # response_data.append(bulk_response_constructor( + # identifier=response_id, + # status = "BAD REQUEST", + # code= 400, + # message= f"Invalid `published_object_id`."\ + # + f"{bco_instance[0]} and {bco_instance[1]}"\ + # + " do not match.", + # )) + # rejected_requests = True + # continue + + # if bco_instance.state == 'PUBLISHED': + # object_id = bco_instance.object_id + # response_data.append(bulk_response_constructor( + # identifier=response_id, + # status = "CONFLICT", + # code= 409, + # message= f"Invalid `object_id`: {object_id} already"\ + # + " exists.", + # )) + # rejected_requests = True + # continue + + # bco_results = validator.parse_and_validate(bco_instance.contents) + # import pdb; pdb.set_trace() + # identifier, results = bco_results.popitem() + + # if results["number_of_errors"] > 0: + # rejected_requests = True + # bco_status = "FAILED" + # status_code = 400 + # message = "BCO not valid" + # else: + # accepted_requests = True + # bco_status = "SUCCESS" + # status_code = 200 + # message = "BCO valid" + + # response_data.append(bulk_response_constructor( + # identifier = identifier, + # status=bco_status, + # code=status_code, + # message=message, + # data=results + # )) + + # status_code = response_status(accepted_requests, rejected_requests) + # return Response(status=status_code, data=response_data) + class DraftsPublishApi(APIView): """Publish Draft BCO [Bulk Enabled] @@ -248,13 +395,13 @@ def post(self, request) -> Response: accepted_requests = False if 'POST_api_objects_drafts_publish' in request.data: data = legacy_api_converter(request.data) - + if "object_id" in data[0] and data[0]["object_id"] == \ f"{hostname}/TEST_000001/DRAFT": identifier= f"{hostname}/TEST_000001/DRAFT" return Response( status=status.HTTP_200_OK, - data=[response_constructor( + data=[bulk_response_constructor( identifier=identifier, status = "SUCCESS", code= 201, @@ -265,10 +412,10 @@ def post(self, request) -> Response: for index, object in enumerate(data): response_id = object.get("object_id", index) - bco_instance = user_can_publish_bco(object, requester) + bco_instance = user_can_publish_draft(object, requester) if bco_instance is None: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "NOT FOUND", code= 404, @@ -278,7 +425,7 @@ def post(self, request) -> Response: continue if bco_instance is False: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "FORBIDDEN", code= 403, @@ -289,7 +436,7 @@ def post(self, request) -> Response: continue if type(bco_instance) is str: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "BAD REQUEST", code= 400, @@ -299,7 +446,7 @@ def post(self, request) -> Response: continue if type(bco_instance) is tuple: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "BAD REQUEST", code= 400, @@ -312,7 +459,7 @@ def post(self, request) -> Response: if bco_instance.state == 'PUBLISHED': object_id = bco_instance.object_id - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "CONFLICT", code= 409, @@ -323,7 +470,7 @@ def post(self, request) -> Response: continue bco_results = validator.parse_and_validate(bco_instance.contents) - import pdb; pdb.set_trace() + identifier, results = bco_results.popitem() if results["number_of_errors"] > 0: @@ -332,12 +479,17 @@ def post(self, request) -> Response: status_code = 400 message = "BCO not valid" else: + publish_draft( + bco_instance=bco_instance, + user=requester, + object=object + ) accepted_requests = True bco_status = "SUCCESS" status_code = 200 message = "BCO valid" - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier = identifier, status=bco_status, code=status_code, @@ -418,7 +570,7 @@ def post(self, request) -> Response: return Response( status=status.HTTP_200_OK, data=[ - response_constructor( + bulk_response_constructor( identifier=test_object_id, status = "SUCCESS", code= 200, @@ -432,7 +584,7 @@ def post(self, request) -> Response: modify_permitted = user_can_modify_bco(response_id, requester) if modify_permitted is None: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "NOT FOUND", code= 404, @@ -442,7 +594,7 @@ def post(self, request) -> Response: continue if modify_permitted is False: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "FORBIDDEN", code= 400, @@ -458,7 +610,7 @@ def post(self, request) -> Response: try: bco_instance = serialized_bco.update(serialized_bco.validated_data) score = bco_instance.score - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SUCCESS", code= 200, @@ -467,7 +619,7 @@ def post(self, request) -> Response: accepted_requests = True except Exception as err: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SERVER ERROR", code= 500, @@ -476,7 +628,7 @@ def post(self, request) -> Response: rejected_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "REJECTED", code= 400, @@ -562,14 +714,14 @@ def post(self, request): bco_status = "FAILED" status_code = 400 message = "BCO not valid" + else: - import pdb; pdb.set_trace() accepted_requests = True bco_status = "SUCCESS" status_code = 200 message = "BCO valid" - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier = identifier, status=bco_status, code=status_code, diff --git a/biocompute/selectors.py b/biocompute/selectors.py index d75f173..96bd087 100644 --- a/biocompute/selectors.py +++ b/biocompute/selectors.py @@ -73,11 +73,11 @@ def prefix_from_object_id(object_id: str) -> str: f"The object ID '{object_id}' does not conform to the expected"\ + "format and the prefix cannot be extracted." ) - -def user_can_publish_bco(object: dict, user:User) -> Bco: - """Publish BCO - Determines if a user has permission to publish a specific BioCompute +def user_can_publish_draft(object: dict, user:User) -> Bco: + """Publish Draft BCO + + Determines if a user has permission to publish a specific Draft BioCompute Object (BCO). Checks if a given user is authorized to publish a BCO identified by its diff --git a/biocompute/urls.py b/biocompute/urls.py index ae04de5..8e21d66 100644 --- a/biocompute/urls.py +++ b/biocompute/urls.py @@ -7,6 +7,7 @@ DraftsCreateApi, DraftsModifyApi, DraftsPublishApi, + PublishBcoApi, ValidateBcoApi, ) @@ -15,4 +16,5 @@ path("objects/drafts/modify/", DraftsModifyApi.as_view()), path("objects/drafts/publish/", DraftsPublishApi.as_view()), path("objects/validate/", ValidateBcoApi.as_view()), + path("objects/publish/", PublishBcoApi.as_view()), ] \ No newline at end of file diff --git a/config/services.py b/config/services.py index 2617985..bf4b77f 100644 --- a/config/services.py +++ b/config/services.py @@ -12,7 +12,7 @@ """ def response_status(accepted_requests: bool, rejected_requests: bool)-> status: - """Determine Response Status + """Determine Response Status for Bulk Requests Determines the appropriate HTTP response status code based on the acceptance or rejection of requests. @@ -79,7 +79,7 @@ def legacy_api_converter(data:dict) ->dict: return new_data -def response_constructor( +def bulk_response_constructor( identifier: str, status: str, code: str, diff --git a/prefix/apis.py b/prefix/apis.py index 3e0cbcc..56ab3ed 100644 --- a/prefix/apis.py +++ b/prefix/apis.py @@ -7,7 +7,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from config.services import legacy_api_converter, response_constructor +from config.services import legacy_api_converter, bulk_response_constructor from prefix.services import PrefixSerializer, delete_prefix from prefix.selectors import get_prefix_object, get_user_prefixes @@ -151,7 +151,7 @@ def post(self, request) -> Response: if data[0]['prefix']=='test' and data[0]['public'] is True: return Response( status=status.HTTP_201_CREATED, - data=[response_constructor( + data=[bulk_response_constructor( identifier="TEST", status = "SUCCESS", code= 201, @@ -165,7 +165,7 @@ def post(self, request) -> Response: if prefix_data.is_valid(): prefix_data.create(prefix_data.validated_data) - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SUCCESS", code= 201, @@ -174,7 +174,7 @@ def post(self, request) -> Response: accepted_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "REJECTED", code= 400, @@ -249,7 +249,7 @@ def post(self, request) -> Response: if data[0] == "TEST": return Response( status=status.HTTP_201_CREATED, - data=[response_constructor( + data=[bulk_response_constructor( identifier="TEST", status = "SUCCESS", code= 200, @@ -261,7 +261,7 @@ def post(self, request) -> Response: response_status = delete_prefix(object, requester) if response_status is True: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SUCCESS", code= 200, @@ -270,7 +270,7 @@ def post(self, request) -> Response: accepted_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "REJECTED", code= 400, @@ -349,7 +349,7 @@ def post(self, request) -> Response: try: if response_object['public'] is True or \ requester.username in response_object['user_permissions']: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SUCCESS", code= 200, @@ -358,7 +358,7 @@ def post(self, request) -> Response: )) accepted_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "FORBIDDEN", code= 403, @@ -368,7 +368,7 @@ def post(self, request) -> Response: except TypeError: if response_object is None: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "NOT FOUND", code= 404, @@ -376,7 +376,7 @@ def post(self, request) -> Response: )) rejected_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "BAD REQUEST", code= 400, @@ -441,7 +441,7 @@ def post(self, request) -> Response: if prefix.is_valid(): if requester == prefix.validated_data['owner']: prefix_update = prefix.update(prefix.validated_data) - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "SUCCESS", code= 200, @@ -451,7 +451,7 @@ def post(self, request) -> Response: accepted_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "REJECTED", code= 400, @@ -461,7 +461,7 @@ def post(self, request) -> Response: rejected_requests = True else: - response_data.append(response_constructor( + response_data.append(bulk_response_constructor( identifier=response_id, status = "REJECTED", code= 400, diff --git a/tests/test_apis/test_biocompute/test_objects_drafts_publish.py b/tests/test_apis/test_biocompute/test_objects_drafts_publish.py index 67b0672..b578c92 100644 --- a/tests/test_apis/test_biocompute/test_objects_drafts_publish.py +++ b/tests/test_apis/test_biocompute/test_objects_drafts_publish.py @@ -5,7 +5,7 @@ DraftsPublishApi: - checks for legacy submission - for each object: - - `user_can_publish_bco`: + - `user_can_publish_draft`: - checks for published_object_id and makes sure it does not exist - checks that DRAFT exists - if published_object_id in request, then checks that published_object_id version matches BCO version