Skip to content

Commit 4e20ffb

Browse files
[PRMP-754] Allow create doc ref service to process other file types (#929)
Co-authored-by: NogaNHS <[email protected]>
1 parent 286402e commit 4e20ffb

28 files changed

+640
-452
lines changed

lambdas/enums/lambda_error.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ def create_error_body(self, params: Optional[dict] = None, **kwargs) -> str:
108108
"message": "ODS code does not match any of the allowed.",
109109
"fhir_coding": FhirIssueCoding.INVALID,
110110
}
111+
DocRefUnauthorizedOdsCode = {
112+
"err_code": "DR_4010",
113+
"message": "The user is not authorised to upload documents for this patient",
114+
"fhir_coding": UKCoreSpineError.ACCESS_DENIED,
115+
}
111116
DocRefPresign = {
112117
"err_code": "DR_5001",
113118
"message": "An error occurred when creating pre-signed url for document reference",

lambdas/enums/snomed_codes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class SnomedCodes(Enum):
1717
GENERAL_MEDICAL_PRACTICE = SnomedCode(
1818
code="1060971000000108", display_name="General practice service"
1919
)
20+
EHR = SnomedCode(code="717301000000104", display_name="Electronic Health Record")
21+
EHR_ATTACHMENTS = SnomedCode(
22+
code="24511000000107", display_name="Electronic Health Record Attachments"
23+
)
2024
# Temporary snomed code used.
2125
PATIENT_DATA = SnomedCode(
2226
code="717391000000106", display_name="Confidential patient data"

lambdas/enums/supported_document_types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
class SupportedDocumentTypes(StrEnum):
1111
ARF = "ARF"
1212
LG = SnomedCodes.LLOYD_GEORGE.value.code
13+
EHR = SnomedCodes.EHR.value.code
14+
EHR_ATTACHMENTS = SnomedCodes.EHR_ATTACHMENTS.value.code
1315

1416
@staticmethod
1517
def list():
@@ -33,12 +35,20 @@ def get_dynamodb_table_name(self) -> str:
3335
document_type_to_table_name = {
3436
SupportedDocumentTypes.ARF: os.getenv("DOCUMENT_STORE_DYNAMODB_NAME"),
3537
SupportedDocumentTypes.LG: os.getenv("LLOYD_GEORGE_DYNAMODB_NAME"),
38+
SupportedDocumentTypes.EHR: os.getenv("LLOYD_GEORGE_DYNAMODB_NAME"),
39+
SupportedDocumentTypes.EHR_ATTACHMENTS: os.getenv(
40+
"LLOYD_GEORGE_DYNAMODB_NAME"
41+
),
3642
}
3743
return document_type_to_table_name[self]
3844

3945
def get_s3_bucket_name(self) -> str:
4046
lookup_dict = {
4147
SupportedDocumentTypes.ARF: os.getenv("DOCUMENT_STORE_BUCKET_NAME"),
4248
SupportedDocumentTypes.LG: os.getenv("LLOYD_GEORGE_BUCKET_NAME"),
49+
SupportedDocumentTypes.EHR: os.getenv("LLOYD_GEORGE_BUCKET_NAME"),
50+
SupportedDocumentTypes.EHR_ATTACHMENTS: os.getenv(
51+
"LLOYD_GEORGE_BUCKET_NAME"
52+
),
4353
}
4454
return lookup_dict[self]
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from enum import StrEnum
2+
from pathlib import Path
3+
from typing import List, Optional
4+
5+
6+
class ForbiddenFileType(StrEnum):
7+
ACTION = "ACTION"
8+
APK = "APK"
9+
APP = "APP"
10+
BAT = "BAT"
11+
BIN = "BIN"
12+
CAB = "CAB"
13+
CMD = "CMD"
14+
COM = "COM"
15+
COMMAND = "COMMAND"
16+
CPL = "CPL"
17+
CSH = "CSH"
18+
EX_ = "EX_"
19+
EXE = "EXE"
20+
GADGET = "GADGET"
21+
INF1 = "INF1"
22+
INS = "INS"
23+
INX = "INX"
24+
IPA = "IPA"
25+
ISU = "ISU"
26+
JOB = "JOB"
27+
JSE = "JSE"
28+
KSH = "KSH"
29+
LNK = "LNK"
30+
MSC = "MSC"
31+
MSI = "MSI"
32+
MSP = "MSP"
33+
MST = "MST"
34+
OSX = "OSX"
35+
OUT = "OUT"
36+
PAF = "PAF"
37+
PIF = "PIF"
38+
PRG = "PRG"
39+
PS1 = "PS1"
40+
RAR = "RAR"
41+
REG = "REG"
42+
RGS = "RGS"
43+
RUN = "RUN"
44+
SCR = "SCR"
45+
SCT = "SCT"
46+
SHB = "SHB"
47+
SHS = "SHS"
48+
U3P = "U3P"
49+
VB = "VB"
50+
VBE = "VBE"
51+
VBS = "VBS"
52+
VBSCRIPT = "VBSCRIPT"
53+
WORKFLOW = "WORKFLOW"
54+
WS = "WS"
55+
WSF = "WSF"
56+
WSH = "WSH"
57+
_7Z = "7Z"
58+
59+
60+
def is_file_type_allowed(
61+
filename: str, accepted_file_types: Optional[List[str]] = None
62+
) -> bool:
63+
"""
64+
if accepted_file_types is empty or None:
65+
→ allow any file except forbidden ones
66+
if accepted_file_types is provided:
67+
→ only allow extensions listed there
68+
"""
69+
if "." not in filename or filename.startswith("."):
70+
return False
71+
72+
path = Path(filename)
73+
extension = path.suffix.upper().lstrip(".")
74+
75+
if extension in {file_type.value for file_type in ForbiddenFileType}:
76+
return False
77+
78+
if accepted_file_types:
79+
return extension in accepted_file_types
80+
81+
# no accepted_file_types provided, accept all non-forbidden types
82+
return True
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Any, List
2+
3+
from pydantic import BaseModel, ConfigDict
4+
from pydantic.alias_generators import to_camel
5+
6+
7+
class DocumentConfig(BaseModel):
8+
model_config = ConfigDict(
9+
validate_by_alias=True,
10+
validate_by_name=True,
11+
populate_by_name=True,
12+
alias_generator=to_camel,
13+
)
14+
snomed_code: str
15+
display_name: str
16+
can_be_updated: bool
17+
associated_snomed: str
18+
multifile_upload: bool
19+
multifile_zipped: bool
20+
multifile_review: bool
21+
can_be_discarded: bool
22+
stitched: bool
23+
accepted_file_types: List[str]
24+
content: List[Any]

lambdas/services/base/s3_service.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,28 @@ def copy_across_bucket(
120120
retry_on_conflict: bool = True,
121121
):
122122
copy_source_params = {"Bucket": source_bucket, "Key": source_file_key}
123-
copy_object_params = {"Bucket": dest_bucket, "Key": dest_file_key, "CopySource": copy_source_params, "StorageClass": "INTELLIGENT_TIERING"}
123+
copy_object_params = {
124+
"Bucket": dest_bucket,
125+
"Key": dest_file_key,
126+
"CopySource": copy_source_params,
127+
"StorageClass": "INTELLIGENT_TIERING",
128+
}
124129
if if_none_match:
125-
copy_object_params["IfNoneMatch"] = '*'
130+
copy_object_params["IfNoneMatch"] = "*"
126131
try:
127132
return self.client.copy_object(**copy_object_params)
128133
except ClientError as e:
129134
if e.response["ResponseMetadata"]["HTTPStatusCode"] == 409:
130135
logger.info(f"Copy failed due to conflict, retrying: {e}")
131136
if retry_on_conflict:
132-
return self.copy_across_bucket(source_bucket, source_file_key, dest_bucket, dest_file_key, if_none_match, False)
137+
return self.copy_across_bucket(
138+
source_bucket,
139+
source_file_key,
140+
dest_bucket,
141+
dest_file_key,
142+
if_none_match,
143+
False,
144+
)
133145
else:
134146
raise e
135147
else:

0 commit comments

Comments
 (0)