Skip to content

Commit a993ea8

Browse files
[PRMP-808] Review - POST request for uploading review items (#937)
1 parent 7752ebb commit a993ea8

25 files changed

+971
-66
lines changed

.github/workflows/base-lambdas-reusable-deploy-all.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,3 +766,17 @@ jobs:
766766
lambda_layer_names: "core_lambda_layer"
767767
secrets:
768768
AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }}
769+
770+
deploy_post_document_review_lambda:
771+
name: Deploy PostDocumentReview lambda
772+
uses: ./.github/workflows/base-lambdas-reusable-deploy.yml
773+
with:
774+
environment: ${{ inputs.environment }}
775+
python_version: ${{ inputs.python_version }}
776+
build_branch: ${{ inputs.build_branch }}
777+
sandbox: ${{ inputs.sandbox }}
778+
lambda_handler_name: post_document_review_handler
779+
lambda_aws_name: PostDocumentReview
780+
lambda_layer_names: "core_lambda_layer"
781+
secrets:
782+
AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }}

lambdas/enums/document_review_status.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ class DocumentReviewStatus(StrEnum):
1010
REASSIGNED = "REASSIGNED"
1111
REASSIGNED_PATIENT_UNKNOWN = "REASSIGNED_PATIENT_UNKNOWN"
1212
NEVER_REVIEWED = "NEVER_REVIEWED"
13+
REVIEW_PENDING_UPLOAD = "REVIEW_PENDING_UPLOAD"
14+
VIRUS_SCAN_FAILED = "VIRUS_SCAN_FAILED"
15+
16+
17+
class DocumentReviewReason(StrEnum):
18+
REVIEW_FROM_DATA_CONTROLLER = "Needs review from data controller"

lambdas/enums/lambda_error.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -661,17 +661,17 @@ def create_error_body(self, params: Optional[dict] = None, **kwargs) -> str:
661661
Errors for DocumentReview exceptions
662662
"""
663663
DocumentReviewDB = {
664-
"err_code": "SDR_5001",
664+
"err_code": "UDR_5001",
665665
"message": RETRIEVE_DOCUMENTS,
666666
}
667667

668668
DocumentReviewValidation = {
669-
"err_code": "SDR_5002",
669+
"err_code": "UDR_5002",
670670
"message": "Review document model error",
671671
}
672672

673673
DocumentReviewMissingODS = {
674-
"err_code": "SDR_4011",
674+
"err_code": "UDR_4001",
675675
"message": "Missing ODS code in request context",
676676
}
677677

@@ -680,13 +680,17 @@ def create_error_body(self, params: Optional[dict] = None, **kwargs) -> str:
680680
"message": "Invalid query string passed",
681681
}
682682

683-
DocumentReviewStatusMissingId = {
684-
"err_code": "SDR_4003",
685-
"message": "Missing path parameters"
683+
DocumentReviewUploadInvalidRequest = {
684+
"err_code": "UDR_4003",
685+
"message": "Invalid request",
686686
}
687687

688-
DocumentReviewForbidden = {
689-
"err_code": "SDR_4031",
690-
"message": "User is not permitted to review document"
688+
DocumentReviewUploadForbidden = {
689+
"err_code": "UDR_4031",
690+
"message": "Forbidden"
691691
}
692692

693+
DocumentReviewPresignedFailure = {
694+
"err_code": "UDR_5003",
695+
"message": "Presign creation process failure",
696+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import json
2+
3+
from enums.feature_flags import FeatureFlags
4+
from enums.lambda_error import LambdaError
5+
from models.document_review import DocumentReviewUploadEvent
6+
from pydantic import ValidationError
7+
from services.feature_flags_service import FeatureFlagService
8+
from services.post_document_review_service import PostDocumentReviewService
9+
from utils.audit_logging_setup import LoggingService
10+
from utils.decorators.ensure_env_var import ensure_environment_variables
11+
from utils.decorators.handle_lambda_exceptions import handle_lambda_exceptions
12+
from utils.decorators.set_audit_arg import set_request_context_for_logging
13+
from utils.exceptions import InvalidNhsNumberException
14+
from utils.lambda_exceptions import DocumentReviewLambdaException
15+
from utils.lambda_response import ApiGatewayResponse
16+
17+
logger = LoggingService("__name__")
18+
19+
20+
@set_request_context_for_logging
21+
@ensure_environment_variables(
22+
names=[
23+
"STAGING_STORE_BUCKET_NAME",
24+
"PRESIGNED_ASSUME_ROLE",
25+
"EDGE_REFERENCE_TABLE",
26+
"CLOUDFRONT_URL",
27+
]
28+
)
29+
@handle_lambda_exceptions
30+
def lambda_handler(event, context):
31+
feature_flag_service = FeatureFlagService()
32+
feature_flag_service.validate_feature_flag(
33+
FeatureFlags.UPLOAD_DOCUMENT_ITERATION_3_ENABLED
34+
)
35+
36+
try:
37+
validated_event_body = validate_event_body(event["body"])
38+
except KeyError as e:
39+
logger.error(e)
40+
raise DocumentReviewLambdaException(
41+
400, LambdaError.DocumentReviewUploadInvalidRequest
42+
)
43+
44+
post_document_review_service = PostDocumentReviewService()
45+
logger.info(f"Processing event: {event}.")
46+
response = post_document_review_service.process_event(event=validated_event_body)
47+
48+
return ApiGatewayResponse(
49+
status_code=200, body=json.dumps(response), methods="POST"
50+
).create_api_gateway_response()
51+
52+
53+
def validate_event_body(body):
54+
try:
55+
event_body = DocumentReviewUploadEvent.model_validate_json(body)
56+
57+
return event_body
58+
except (ValidationError, InvalidNhsNumberException) as e:
59+
logger.error(e)
60+
raise DocumentReviewLambdaException(
61+
400, LambdaError.DocumentReviewUploadInvalidRequest
62+
)

lambdas/handlers/review_document_status_check_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from utils.decorators.handle_lambda_exceptions import handle_lambda_exceptions
1212
from utils.decorators.set_audit_arg import set_request_context_for_logging
1313
from utils.exceptions import OdsErrorException
14-
from utils.lambda_exceptions import DocumentReviewException
14+
from utils.lambda_exceptions import DocumentReviewLambdaException
1515
from utils.lambda_handler_utils import validate_review_path_parameters
1616
from utils.lambda_response import ApiGatewayResponse
1717
from utils.ods_utils import extract_ods_code_from_request_context
@@ -68,4 +68,4 @@ def lambda_handler(event, context):
6868

6969
except OdsErrorException:
7070
logger.error("Missing ODS code in request context.")
71-
raise DocumentReviewException(401, LambdaError.DocumentReviewMissingODS)
71+
raise DocumentReviewLambdaException(401, LambdaError.DocumentReviewMissingODS)

lambdas/handlers/search_document_review_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from utils.decorators.override_error_check import override_error_check
1414
from utils.decorators.set_audit_arg import set_request_context_for_logging
1515
from utils.exceptions import OdsErrorException
16-
from utils.lambda_exceptions import DocumentReviewException
16+
from utils.lambda_exceptions import DocumentReviewLambdaException
1717
from utils.lambda_response import ApiGatewayResponse
1818
from utils.request_context import request_context
1919

@@ -54,7 +54,7 @@ def lambda_handler(event, context):
5454
FeatureFlags.UPLOAD_DOCUMENT_ITERATION_3_ENABLED
5555
]:
5656
logger.info("Feature flag not enabled, event will not be processed")
57-
raise DocumentReviewException(403, LambdaError.FeatureFlagDisabled)
57+
raise DocumentReviewLambdaException(403, LambdaError.FeatureFlagDisabled)
5858

5959
ods_code = get_ods_code_from_request_context()
6060

@@ -99,7 +99,7 @@ def get_ods_code_from_request_context():
9999

100100
except AttributeError as e:
101101
logger.error(e)
102-
raise DocumentReviewException(401, LambdaError.DocumentReviewMissingODS)
102+
raise DocumentReviewLambdaException(401, LambdaError.DocumentReviewMissingODS)
103103

104104

105105
def parse_querystring_parameters(event):

lambdas/models/document_review.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import uuid
2+
from datetime import datetime, timezone
23

34
from enums.document_review_status import DocumentReviewStatus
45
from enums.metadata_field_names import DocumentReferenceMetadataFields
56
from enums.snomed_codes import SnomedCodes
6-
from pydantic import BaseModel, ConfigDict, Field, model_validator
7+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
78
from pydantic.alias_generators import to_camel, to_pascal
89
from utils.exceptions import InvalidNhsNumberException
910
from utils.utilities import validate_nhs_number
@@ -17,7 +18,7 @@ class DocumentReviewFileDetails(BaseModel):
1718
)
1819

1920
file_name: str
20-
file_location: str
21+
file_location: str | None = None
2122
presigned_url: str | None = None
2223

2324

@@ -37,10 +38,12 @@ class DocumentUploadReviewReference(BaseModel):
3738
review_status: DocumentReviewStatus = Field(
3839
default=DocumentReviewStatus.PENDING_REVIEW
3940
)
40-
review_reason: str
41+
review_reason: str | None = None
4142
review_date: int | None = Field(default=None)
4243
reviewer: str | None = Field(default=None)
43-
upload_date: int
44+
upload_date: int = Field(
45+
default_factory=lambda: int(datetime.now(timezone.utc).timestamp()),
46+
)
4447
files: list[DocumentReviewFileDetails] = Field(min_length=1)
4548
nhs_number: str
4649
version: int = Field(default=1)
@@ -126,3 +129,29 @@ def validate_reassign_nhs_number(self):
126129
except InvalidNhsNumberException:
127130
raise ValueError("Invalid NHS number")
128131
return self
132+
133+
134+
class DocumentReviewUploadEvent(BaseModel):
135+
model_config = ConfigDict(
136+
validate_by_alias=True,
137+
populate_by_name=True,
138+
validate_by_name=True,
139+
alias_generator=to_camel,
140+
use_enum_values=True,
141+
extra="forbid",
142+
)
143+
144+
nhs_number: str
145+
snomed_code: SnomedCodes
146+
documents: list = Field(min_length=1, max_length=1)
147+
148+
@field_validator("snomed_code", mode="before")
149+
@classmethod
150+
def check_snomed_code(cls, value) -> SnomedCodes | None:
151+
return SnomedCodes.find_by_code(value)
152+
153+
@field_validator("nhs_number", mode="before")
154+
@classmethod
155+
def verify_nhs_number(cls, value) -> str | None:
156+
if validate_nhs_number(value):
157+
return value

lambdas/services/base/dynamo_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import operator
12
import time
2-
from typing import Optional, Sequence
33
from functools import reduce
4+
from typing import Optional, Sequence
45

56
import boto3
6-
import operator
77
from boto3.dynamodb.conditions import Attr, ConditionBase, Key
88
from botocore.exceptions import ClientError
99
from utils.audit_logging_setup import LoggingService

lambdas/services/document_service.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Optional
44

55
from boto3.dynamodb.conditions import Attr, ConditionBase
6+
from botocore.exceptions import ClientError
67
from enums.metadata_field_names import DocumentReferenceMetadataFields
78
from enums.supported_document_types import SupportedDocumentTypes
89
from models.document_reference import DocumentReference
@@ -279,3 +280,20 @@ def get_batch_document_references_by_id(
279280

280281
found_docs = [model_class.model_validate(item) for item in response]
281282
return found_docs
283+
284+
def create_dynamo_entry(
285+
self,
286+
item: BaseModel,
287+
table_name: str | None = None,
288+
model_class: BaseModel | None = None,
289+
):
290+
try:
291+
table_name = table_name or self.table_name
292+
model_class = model_class or self.model_class
293+
294+
model_class.model_validate(item)
295+
entry = item.model_dump(by_alias=True, exclude_none=True)
296+
self.dynamo_service.create_item(table_name=table_name, item=entry)
297+
except (ValidationError, ClientError) as e:
298+
logger.error(e)
299+
raise e

0 commit comments

Comments
 (0)