Skip to content

Commit cacd264

Browse files
committed
Merge branch 'feature/PI-572-create_an_as_device' into release/2024-12-02
2 parents 88be9aa + 301891c commit cacd264

File tree

18 files changed

+1438
-0
lines changed

18 files changed

+1438
-0
lines changed

infrastructure/swagger/05_paths.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,32 @@ paths:
385385
- ${authoriser_name}: []
386386
- app-level0: []
387387

388+
/ProductTeam/{product_team_id}/Product/{product_id}/Device/AccreditedSystem:
389+
post:
390+
operationId: createDeviceAccreditedSystem
391+
summary: Create a Accredited System Device resource (POST)
392+
parameters:
393+
- $ref: "#/components/parameters/ProductTeamId"
394+
- $ref: "#/components/parameters/ProductId"
395+
- $ref: "#/components/parameters/HeaderVersion"
396+
- $ref: "#/components/parameters/HeaderRequestId"
397+
- $ref: "#/components/parameters/HeaderCorrelationId"
398+
requestBody:
399+
$ref: "#/components/requestBodies/AccreditedSystemDeviceCreateRequestBody"
400+
responses:
401+
"201":
402+
$ref: "#/components/responses/AsDeviceCreate"
403+
"400":
404+
$ref: "#/components/responses/BadRequest"
405+
"404":
406+
$ref: "#/components/responses/NotFound"
407+
x-amazon-apigateway-integration:
408+
<<: *ApiGatewayIntegration
409+
uri: ${method_createDeviceAccreditedSystem}
410+
security:
411+
- ${authoriser_name}: []
412+
- app-level0: []
413+
388414
/searchSdsDevice:
389415
get:
390416
operationId: searchsdsdevice

infrastructure/swagger/07_components--schemas--domain.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,32 @@ components:
167167
questionnaire_responses:
168168
type: object
169169

170+
AsDeviceResponse:
171+
type: object
172+
properties:
173+
id:
174+
type: string
175+
name:
176+
type: string
177+
product_id:
178+
type: string
179+
product_team_id:
180+
type: string
181+
ods_code:
182+
type: string
183+
status:
184+
type: string
185+
created_on:
186+
type: string
187+
updated_on:
188+
type: string
189+
nullable: true
190+
deleted_on:
191+
type: string
192+
nullable: true
193+
questionnaire_responses:
194+
type: object
195+
170196
MhsDeviceResponse:
171197
type: object
172198
properties:

infrastructure/swagger/11_components--requestBodies.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,25 @@ components:
144144
- questionnaire_responses
145145
example:
146146
questionnaire_responses: { "spine_mhs": [{ "question": "answer" }] }
147+
AccreditedSystemDeviceCreateRequestBody:
148+
description: Request body to create a Accredited System Device object
149+
required: true
150+
content:
151+
application/json:
152+
schema:
153+
type: object
154+
properties:
155+
questionnaire_responses:
156+
type: object
157+
description: Questionnaire Responses for AS Device
158+
properties:
159+
spine_as:
160+
type: array
161+
description: spine_as questionnaire associated with the as device
162+
items:
163+
type: object
164+
description: spine_as questionnaire response
165+
required:
166+
- questionnaire_responses
167+
example:
168+
questionnaire_responses: { "spine_as": [{ "question": "answer" }] }

infrastructure/swagger/12_components--responses.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ components:
165165
application/json:
166166
schema:
167167
$ref: "#/components/schemas/DeviceResponse"
168+
AsDeviceCreate:
169+
description: Create Accredited System Device operation successful
170+
content:
171+
application/json:
172+
schema:
173+
$ref: "#/components/schemas/AsDeviceResponse"
168174
MhsDeviceCreate:
169175
description: Create Message Handling System Device operation successful
170176
content:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from api_utils.api_step_chain import execute_step_chain
2+
from event.aws.client import dynamodb_client
3+
from event.environment import BaseEnvironment
4+
from event.logging.logger import setup_logger
5+
6+
from .src.v1.steps import steps as v1_steps
7+
8+
9+
class Environment(BaseEnvironment):
10+
DYNAMODB_TABLE: str
11+
12+
13+
versioned_steps = {"1": v1_steps}
14+
cache = {
15+
**Environment.build().dict(),
16+
"DYNAMODB_CLIENT": dynamodb_client(),
17+
}
18+
19+
20+
def handler(event: dict, context=None):
21+
setup_logger(service_name=__file__)
22+
return execute_step_chain(
23+
event=event,
24+
cache=cache,
25+
versioned_steps=versioned_steps,
26+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from builder.lambda_build import build
2+
3+
if __name__ == "__main__":
4+
build(__file__)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
"dynamodb:Query",
3+
"dynamodb:PutItem",
4+
"dynamodb:UpdateItem",
5+
"dynamodb:GetItem"
6+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
["kms:Decrypt"]
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
from http import HTTPStatus
2+
3+
from domain.api.common_steps.general import parse_event_body
4+
from domain.api.common_steps.read_product import (
5+
parse_path_params,
6+
read_product,
7+
read_product_team,
8+
)
9+
from domain.core.cpm_product import CpmProduct
10+
from domain.core.device import (
11+
Device,
12+
DeviceTagAddedEvent,
13+
QuestionnaireResponseUpdatedEvent,
14+
)
15+
from domain.core.device_reference_data import DeviceReferenceData
16+
from domain.core.error import (
17+
AccreditedSystemFatalError,
18+
ConfigurationError,
19+
NotEprProductError,
20+
)
21+
from domain.core.product_key import ProductKeyType
22+
from domain.core.questionnaire import Questionnaire, QuestionnaireResponse
23+
from domain.repository.device_reference_data_repository import (
24+
DeviceReferenceDataRepository,
25+
)
26+
from domain.repository.device_repository import DeviceRepository
27+
from domain.repository.questionnaire_repository import (
28+
QuestionnaireInstance,
29+
QuestionnaireRepository,
30+
)
31+
from domain.request_models import CpmProductPathParams, CreateAsDeviceIncomingParams
32+
from domain.response.validation_errors import mark_validation_errors_as_inbound
33+
from sds.epr.constants import EprNameTemplate
34+
35+
36+
@mark_validation_errors_as_inbound
37+
def parse_as_device_payload(data, cache) -> CreateAsDeviceIncomingParams:
38+
payload: dict = data[parse_event_body]
39+
return CreateAsDeviceIncomingParams(**payload)
40+
41+
42+
def read_device_reference_data(data, cache) -> list[DeviceReferenceData]:
43+
path_params: CpmProductPathParams = data[parse_path_params]
44+
product_repo = DeviceReferenceDataRepository(
45+
table_name=cache["DYNAMODB_TABLE"], dynamodb_client=cache["DYNAMODB_CLIENT"]
46+
)
47+
device_reference_data = product_repo.search(
48+
product_id=path_params.product_id,
49+
product_team_id=path_params.product_team_id,
50+
)
51+
52+
# Only 1 AS DRD and only 1 MHS DRD
53+
54+
if not device_reference_data or len(device_reference_data) < 2:
55+
raise ConfigurationError(
56+
"You must configure the AS and MessageSet Device Reference Data before creating an AS Device"
57+
)
58+
59+
if len(device_reference_data) > 2:
60+
raise AccreditedSystemFatalError(
61+
"More that 2 MessageSet Device Reference Data resources were found. This is not allowed"
62+
)
63+
64+
exists = set()
65+
if any(drd.name in exists or exists.add(drd.name) for drd in device_reference_data):
66+
raise ConfigurationError(
67+
"Only one AS and one MessageSet Device Reference Data is allowed before creating an AS Device"
68+
)
69+
70+
return device_reference_data
71+
72+
73+
def read_spine_as_questionnaire(data, cache) -> Questionnaire:
74+
return QuestionnaireRepository().read(QuestionnaireInstance.SPINE_AS)
75+
76+
77+
def validate_spine_as_questionnaire_response(data, cache) -> QuestionnaireResponse:
78+
spine_as_questionnaire: Questionnaire = data[read_spine_as_questionnaire]
79+
payload: CreateAsDeviceIncomingParams = data[parse_as_device_payload]
80+
spine_as_questionnaire_response = payload.questionnaire_responses[
81+
QuestionnaireInstance.SPINE_AS
82+
]
83+
return spine_as_questionnaire.validate(
84+
data=spine_as_questionnaire_response.__root__[0]
85+
)
86+
87+
88+
def create_as_device(data, cache) -> Device:
89+
product: CpmProduct = data[read_product]
90+
payload: CreateAsDeviceIncomingParams = data[parse_as_device_payload]
91+
party_key: str = data[get_party_key]
92+
93+
# Create a new Device dictionary excluding 'questionnaire_responses'
94+
# Ticket PI-666 adds ASID generation. This will need to be sent across in the arguments instead of an empty string.
95+
device_payload = payload.dict(exclude={"questionnaire_responses"})
96+
return product.create_device(
97+
name=EprNameTemplate.AS_DEVICE.format(party_key=party_key, asid=""),
98+
**device_payload
99+
)
100+
101+
102+
def create_party_key_tag(data, cache) -> DeviceTagAddedEvent:
103+
as_device: Device = data[create_as_device]
104+
return as_device.add_tag(party_key=data[get_party_key])
105+
106+
107+
def create_device_keys(data, cache) -> Device:
108+
# We will need to add some keys in the future, ASID?
109+
as_device: Device = data[create_as_device]
110+
return as_device
111+
112+
113+
def add_device_reference_data_id(data, cache) -> Device:
114+
as_device: Device = data[create_device_keys]
115+
device_reference_data: DeviceReferenceData = data[read_device_reference_data]
116+
for drd in device_reference_data:
117+
as_device.add_device_reference_data_id(
118+
device_reference_data_id=str(drd.id), path_to_data=["*.Interaction ID"]
119+
)
120+
return as_device
121+
122+
123+
def add_spine_as_questionnaire_response(
124+
data, cache
125+
) -> QuestionnaireResponseUpdatedEvent:
126+
spine_as_questionnaire_response: QuestionnaireResponse = data[
127+
validate_spine_as_questionnaire_response
128+
]
129+
as_device: Device = data[create_as_device]
130+
131+
return as_device.add_questionnaire_response(spine_as_questionnaire_response)
132+
133+
134+
def write_device(data: dict[str, Device], cache) -> Device:
135+
as_device: Device = data[create_as_device]
136+
repo = DeviceRepository(
137+
table_name=cache["DYNAMODB_TABLE"], dynamodb_client=cache["DYNAMODB_CLIENT"]
138+
)
139+
return repo.write(as_device)
140+
141+
142+
def set_http_status(data, cache) -> tuple[HTTPStatus, dict]:
143+
as_device: Device = data[create_as_device]
144+
return HTTPStatus.CREATED, as_device.state_exclude_tags()
145+
146+
147+
def get_party_key(data, cache) -> str:
148+
product: CpmProduct = data[read_product]
149+
party_keys = (
150+
key.key_value
151+
for key in product.keys
152+
if key.key_type is ProductKeyType.PARTY_KEY
153+
)
154+
try:
155+
(party_key,) = party_keys
156+
except ValueError:
157+
raise NotEprProductError(
158+
"Not an EPR Product: Cannot create AS device for product without exactly one Party Key"
159+
)
160+
return party_key
161+
162+
163+
steps = [
164+
parse_event_body,
165+
parse_path_params,
166+
parse_as_device_payload,
167+
read_product_team,
168+
read_product,
169+
get_party_key,
170+
read_device_reference_data,
171+
read_spine_as_questionnaire,
172+
validate_spine_as_questionnaire_response,
173+
create_as_device,
174+
create_party_key_tag,
175+
create_device_keys,
176+
add_device_reference_data_id,
177+
add_spine_as_questionnaire_response,
178+
write_device,
179+
set_http_status,
180+
]

0 commit comments

Comments
 (0)