Skip to content

Commit b18a198

Browse files
authored
Merge pull request #9 from aasaru/main3
Improvements
2 parents bbd4298 + 90f3f15 commit b18a198

20 files changed

Lines changed: 366 additions & 400 deletions

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ RUN mkdir -p config
1010
COPY config/docker.cfg config/
1111
COPY settings.yaml .
1212

13-
EXPOSE 5000
13+
EXPOSE 8082
1414

1515
ENV PYTHONPATH=/app
1616
ENV APP_SETTINGS=../config/docker.cfg

README.md

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11

22
# Adapter application (Python)
33

4-
This application is an adapter for Pääsuke written in Python Flask.
5-
This is used by a party who keeps mandates on their side but offers a standard service
4+
This application is an adapter for [Pääsuke](https://github.com/e-gov/PH) written in Python Flask.
5+
This can be used by a party who keeps mandates on their side and offers a standard web service
66
for Pääsuke for it to query mandates.
77

88
The idea is that there is a Postgres database that keeps the data.
9-
Into that Postgres database we create view and procedures that this app calls.
9+
Into that Postgres database we create view and stored procedures that this app calls.
1010

1111
## Sequence diagram illustrating the application
1212
![Sequence diagram](doc/sequence-diagram-pr.png)
@@ -31,8 +31,6 @@ This database has a few tables to sore information about mandates.
3131

3232

3333
## Configuration
34-
1. List of roles => view
35-
3634

3735
## How to run API app in development environment
3836

@@ -52,18 +50,15 @@ Check configuration example in example.cfg.
5250
## How to run API app with docker-compose
5351
`docker-compose up -d`
5452

55-
Access API on http://localhost:5002/v1
53+
## How to configure the list of roles
54+
55+
Configure the list of roles in tests/pg_data/02b_view_paasuke_roles_view.sql
5656

57-
```
58-
curl --location 'http://localhost:5002/v1/representees/EE33333333/delegates/mandates' \
59-
--header 'X-Road-UserId: test-header-xroad-userid' \
60-
--header 'X-Road-Represented-Party: TEST-Represented-Party' \
61-
--header 'X-Road-Id: TEST-ID-12345'
62-
```
6357

64-
## Tests
58+
## How to run tests
6559

66-
Tests are using the Postgres database running on a Docker container and fixture data inside.
60+
Tests are using the Postgres database running on a Docker container (so you need to have `docker-compose up postgres`)
61+
Database contains fixture data .
6762
`python3 -m venv venv`
6863
`source venv/bin/activate`
6964
`pip install -r requirements.txt`
@@ -82,17 +77,20 @@ Point gunicorn to WSGI entrypoint `wsgi.py`
8277

8378
## Endpoints
8479

80+
When running locally then access API on http://localhost:8082
81+
82+
8583
### `GET /v1/representees/<str:representee>/delegates/mandates`
8684

8785
Accepts representee identifier as parameter in path
88-
Raises `400` error if identifier does not validate
86+
Raises `400` error if identifier is not valid
8987
Returns the list of `MandateTriplet`s with status code `200` if representee has valid mandates
9088
Returns empty list if the representee has no valid mandates or the representee is unknown.
91-
Uses Postgres view `representee_mandates_view`
89+
Selects data from Postgres view `paasuke_mandates_view`
9290

9391
Example:
9492
```
95-
curl --location 'http://localhost:5002/v1/representees/EE33333333/delegates/mandates' \
93+
curl --location 'http://localhost:8082/v1/representees/EE44444444/delegates/mandates' \
9694
--header 'X-Road-UserId: test-header-xroad-userid' \
9795
--header 'X-Road-Represented-Party: test-header-xroad-represented-party' \
9896
--header 'X-Road-Id: test-header-xroad-id'
@@ -102,49 +100,54 @@ curl --location 'http://localhost:5002/v1/representees/EE33333333/delegates/mand
102100
### `GET /v1/delegates/<str:delegate>/representees/mandates`
103101

104102
Accepts delegate identifier as parameter in path
105-
Raises `400` error if identifier does not validate
103+
Raises `400` error if identifier is not valid
106104
Returns the list of `MandateTriplet`s with status code `200` if delegate has valid mandates
107105
Returns an empty list if delegate has no valid mandates or the delegate is unknown.
108-
Uses Postgres view `representee_mandates_view`
106+
Selects data from Postgres view `paasuke_mandates_view`
109107

110108
Example:
111109
```
112-
curl --location 'http://127.0.0.1:5002/v1/delegates/EE1111111/representees/mandates' \
110+
curl --location 'http://127.0.0.1:8082/v1/delegates/EE22202222222/representees/mandates' \
113111
--header 'X-Road-UserId: Test User Id'
114112
```
115113

116114
### `GET /roles`
117115

118116
Get a list of roles
119-
Selects data from Postgres view `roles_view`
117+
Selects data from Postgres view `paasuke_roles_view`
120118

121119
Example:
122120

123-
`curl --location 'http://localhost:5002/v1/roles`
121+
`curl --location 'http://localhost:8082/v1/roles'`
124122

125123

126124

127125
### `POST /v1/representees/<str:representee>/delegates/<str:delegate>/mandates`
128126

129127
Accepts repreentee and delegate identifiers as path parameters
130128
Raises `400` error if payload data is not valid or identifiers are not valid
131-
Raises `422` error if Postgres function `function_create_mandate` does not validate input data.
129+
Raises `422` error if Postgres function `paasuke_add_mandate` does not validate input data.
132130
Return an empty list with status code `201` in case of success
133131

134132
Example of successful request:
135133

136134
```
137-
curl --location 'http://127.0.0.1:5002/v1/representees/EE12345678/delegates/EE38302250123/mandates' \
135+
curl --location 'http://127.0.0.1:8082/v1/representees/EE12345678/delegates/EE38302250123/mandates' \
138136
--header 'Content-Type: application/json' \
139137
--header 'X-Road-UserId: LT123456' \
140138
--header 'X-Road-Represented-Party: LV1234566' \
141139
--data ' {
142140
"authorizations": [
143141
{
144-
"hasRole": "FROM_BUSINESS_REGISTRY:MANAGEMENT_BOARD_MEMBER_FULL",
142+
"hasRole": "BR_REPRIGHT:JUHL_SOLEREP",
145143
"userIdentifier": "EE49028099999"
146144
}
147145
],
146+
"representee": {
147+
"identifier": "EE12345678",
148+
"legalName": "Väikefirma OÜ",
149+
"type": "LEGAL_PERSON"
150+
},
148151
"delegate": {
149152
"firstName": "Jüri",
150153
"identifier": "EE38302250123",
@@ -157,16 +160,11 @@ curl --location 'http://127.0.0.1:5002/v1/representees/EE12345678/delegates/EE38
157160
},
158161
"mandate": {
159162
"canSubDelegate": true,
160-
"role": "GLOBAL1_EMTA:ACCOUNTANT",
163+
"role": "AGENCY_X:MANDATES_MANAGER",
161164
"validityPeriod": {
162165
"from": "2028-01-01",
163166
"through": "2030-12-31"
164167
}
165-
},
166-
"representee": {
167-
"identifier": "EE12345678",
168-
"legalName": "Väikefirma OÜ",
169-
"type": "LEGAL_PERSON"
170168
}
171169
}'
172170
```
@@ -176,19 +174,20 @@ curl --location 'http://127.0.0.1:5002/v1/representees/EE12345678/delegates/EE38
176174

177175
Accepts `representeeId`, `delegateId`, `mandateId` as parameters in the path.
178176
Raises `404` error if the mandate does not exist
179-
Raises `422` error if Postgres function `function_insert_mandate_subdelegate` does not validate input data.
177+
Raises `422` error if Postgres function `paasuke_add_mandate_subdelegate` does not validate input data.
180178
Returns empty list with status code `200` in success case
181179

182180
Example of successful request:
181+
183182
```
184-
curl --location 'http://127.0.0.1:5002/v1/representees/EE33333333/delegates/100001/mandates/150003/subdelegates' \
183+
curl --location 'http://127.0.0.1:8082/v1/representees/100004/delegates/100005/mandates/150003/subdelegates' \
185184
--header 'Content-Type: application/json' \
186185
--header 'X-Road-UserId: EE23232323' \
187186
--header 'X-Road-Represented-Party: EE2323224444' \
188187
--data '{
189188
"authorizations": [
190189
{
191-
"hasRole": "FROM_BUSINESS_REGISTRY:MANAGEMENT_BOARD_MEMBER_FULL",
190+
"hasRole": "BR_REPRIGHT:PROK_SOLEREP",
192191
"userIdentifier": "EE39912310123"
193192
}
194193
],
@@ -214,13 +213,12 @@ curl --location 'http://127.0.0.1:5002/v1/representees/EE33333333/delegates/1000
214213

215214
Accepts `representeeId`, `delegateId`, `mandateId` as parameters in the path.
216215
Raises `404` error if the mandate does not exist
217-
Raises `422` error if Postgres function `function_delete_mandate` does not validate input data.
218-
Returns empty list with status code `200` in success case
216+
Raises `422` error if Postgres function `paasuke_delete_mandate` does not validate input data.
217+
Returns empty list with status code `200` if mandates has been marked deleted successfully
219218

220219
Example of successful request:
221-
222220
```
223-
curl --location --request PUT 'http://127.0.0.1:5002/v1/representees/EE33333333/delegates/100001/mandates/150003' \
221+
curl --location --request PUT 'http://127.0.0.1:8082/v1/representees/100001/delegates/100003/mandates/150002' \
224222
--header 'Content-Type: application/json' \
225223
--data '{
226224
"action": "DELETE",

api/app.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,22 @@ def handle_unhandled_error(e):
6565
app.logger.exception('Unexpected error occurred: %s', e)
6666
return jsonify(base.to_dict()), 500
6767

68-
@app.route('/v1/delegates/<string:delegate_id>/representees/mandates', methods=['GET'])
69-
def get_delegates_representees_mandates(delegate_id):
68+
@app.route('/v1/delegates/<string:delegate_identifier>/representees/mandates', methods=['GET'])
69+
def get_delegates_representees_mandates(delegate_identifier):
7070
xroad_user_id = request.headers.get('X-Road-UserId')
7171
app.logger.info(f'X-Road-UserId: {xroad_user_id} Getting delegate mandates')
7272
error_config = app.config['SETTINGS']['errors']['legal_person_format_validation_failed']
73-
validate_person_company_code(delegate_id, error_config)
73+
validate_person_company_code(delegate_identifier, error_config)
7474

75-
data_rows = get_mandates(db, delegate_identifier=delegate_id)
75+
data_rows = get_mandates(db, delegate_identifier=delegate_identifier)
7676
if not data_rows:
7777
return make_success_response(data_rows, 200)
7878
delegate, representees = extract_delegates_mandates(data_rows)
7979
response_data = serialize_delegate_mandates(delegate, representees, app.config['SETTINGS'])
8080
return make_success_response(response_data, 200)
8181

82-
@app.route('/v1/representees/<string:representee_id>/delegates/mandates', methods=['GET'])
83-
def get_representees_delegates_mandates(representee_id):
82+
@app.route('/v1/representees/<string:representee_identifier>/delegates/mandates', methods=['GET'])
83+
def get_representees_delegates_mandates(representee_identifier):
8484
xroad_user_id = request.headers.get('X-Road-UserId')
8585
app.logger.info(f'X-Road-UserId: {xroad_user_id} Getting representee mandates')
8686

@@ -91,11 +91,11 @@ def get_representees_delegates_mandates(representee_id):
9191
error_config = app.config['SETTINGS']['errors']['legal_person_format_validation_failed']
9292
[
9393
validate_person_company_code(code, error_config)
94-
for code in [representee_id, delegate_identifier, subdelegated_by_identifier]
94+
for code in [representee_identifier, delegate_identifier, subdelegated_by_identifier]
9595
]
9696
data_rows = get_mandates(
9797
db,
98-
representee_identifier=representee_id,
98+
representee_identifier=representee_identifier,
9999
delegate_identifier=delegate_identifier,
100100
subdelegated_by_identifier=subdelegated_by_identifier
101101
)
@@ -106,24 +106,24 @@ def get_representees_delegates_mandates(representee_id):
106106
response_data = serialize_representee_mandates(representee, delegates, app.config['SETTINGS'])
107107
return make_success_response(response_data, 200)
108108

109-
@app.route('/v1/representees/<string:representee_id>/delegates/<string:delegate_id>/mandates', methods=['POST'])
110-
def post_representee_delegate_mandate(representee_id, delegate_id):
109+
@app.route('/v1/representees/<string:representee_identifier>/delegates/<string:delegate_identifier>/mandates',
110+
methods=['POST'])
111+
def post_representee_delegate_mandate(representee_identifier, delegate_identifier):
111112
xroad_user_id = request.headers.get('X-Road-UserId')
112113
xroad_represented_party = request.headers.get('X-Road-Represented-Party')
113114
app.logger.info(f'X-Road-UserId: {xroad_user_id} is about to add a mandate')
114115

115116
error_config = app.config['SETTINGS']['errors']['legal_person_format_validation_failed']
116117
[
117118
validate_person_company_code(code, error_config)
118-
for code in [representee_id, delegate_id]
119+
for code in [representee_identifier, delegate_identifier]
119120
]
120121
data = request.json
121122
error_config = app.config['SETTINGS']['errors']['mandate_data_invalid']
122-
validate_add_mandate_payload(data, error_config, representee_id, delegate_id)
123+
validate_add_mandate_payload(data, error_config, representee_identifier, delegate_identifier)
123124

124125
data_to_insert = extract_mandate_data(data)
125126
data_to_insert['data_created_by'] = xroad_user_id
126-
data_to_insert['data_created_by_represented_person'] = xroad_represented_party
127127
db_uri = app.config['SQLALCHEMY_DATABASE_URI']
128128
try:
129129
create_mandate_pg(db_uri, data_to_insert)
@@ -183,7 +183,6 @@ def post_subdelegate_mandate(representee_id, delegate_id, mandate_id):
183183
data_to_insert['mandate_id'] = mandate_id
184184

185185
data_to_insert['data_created_by'] = xroad_user_id
186-
data_to_insert['data_created_by_represented_person'] = xroad_represented_party
187186
try:
188187
result = subdelegate_mandate_pg(app.config['SQLALCHEMY_DATABASE_URI'], data_to_insert)
189188
except psycopg2.errors.RaiseException as e:
@@ -253,5 +252,5 @@ def get_roles():
253252
app.run(
254253
debug=app.config.get('DEBUG'),
255254
host=app.config.get('HOST', '0.0.0.0'),
256-
port=app.config.get('PORT', 5000)
255+
port=app.config.get('PORT', 8082)
257256
)

api/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ class UnprocessableRequestError(ErrorConfigBase):
6565
def __init__(self, *args, **kwargs) -> None:
6666
super().__init__(*args, **kwargs)
6767
status_code = kwargs.get('status_code')
68-
self.status_code = status_code or UnprocessableRequestError.status_code
68+
self.status_code = status_code or UnprocessableRequestError.status_code

api/serializers.py

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ def serialize_delegate_mandates(delegate, representees, settings):
22
data = []
33
for representee in representees:
44
item = {
5+
'representee': {
6+
'identifier': representee['representee_identifier'],
7+
'legalName': representee['representee_legal_name'],
8+
'type': representee['representee_type']
9+
},
510
'delegate': {
611
'firstName': delegate['delegate_first_name'],
712
'identifier': delegate['delegate_identifier'],
813
'surname': delegate['delegate_surname'],
914
'type': delegate['delegate_type']
1015
},
11-
'mandates': [],
12-
'representee': {
13-
'identifier': representee['representee_identifier'],
14-
'legalName': representee['representee_legal_name'],
15-
'type': representee['representee_type']
16-
}
16+
'mandates': []
1717
}
1818
for mandate in representee['mandates']:
1919
mandate_data = serialize_mandate(representee, delegate, mandate, settings)
@@ -26,9 +26,9 @@ def serialize_representee_mandates(representee, delegates, settings):
2626
response_data = []
2727
for delegate in delegates:
2828
item = {
29+
'representee': serialize_item_by_type(representee, 'representee'),
2930
'delegate': serialize_item_by_type(delegate, 'delegate'),
3031
'mandates': [],
31-
'representee': serialize_item_by_type(representee, 'representee'),
3232
}
3333
for mandate in delegate['mandates']:
3434
mandate_data = serialize_mandate(representee, delegate, mandate, settings)
@@ -70,14 +70,17 @@ def serialize_item_by_type(item, key_type):
7070

7171

7272
def serialize_mandate(representee, delegate, mandate, settings):
73-
links = {
74-
'delete': mandate['link_delete'],
75-
'origin': settings["origin_url"],
76-
}
73+
links = {}
74+
75+
if mandate['link_delete']:
76+
links['delete'] = mandate['link_delete']
7777

7878
if mandate['can_sub_delegate'] and mandate['link_add_sub_delegate']:
7979
links['addSubDelegate'] = mandate['link_add_sub_delegate']
8080

81+
if mandate['link_origin']:
82+
links['origin'] = mandate['link_origin']
83+
8184
validity_period = {}
8285
if mandate['validity_period_from']:
8386
validity_period['from'] = mandate['validity_period_from'].strftime('%Y-%m-%d')
@@ -87,23 +90,10 @@ def serialize_mandate(representee, delegate, mandate, settings):
8790
mandate_data = {
8891
'links': links,
8992
'role': mandate['role'],
90-
**({'subDelegatorIdentifier': mandate['created_by_represented_person']}
91-
if mandate['original_mandate_id'] and mandate['created_by_represented_person'] else {}),
93+
**({'subDelegatorIdentifier': mandate['subdelegated_by_identifier']}
94+
if mandate['subdelegated_by_identifier'] else {}),
9295
**({'validityPeriod': validity_period}
9396
if validity_period else {}),
9497
}
9598
return mandate_data
9699

97-
98-
def set_subdelegate_link(mandate, representee, delegate):
99-
representee_id = representee['representee_id']
100-
delegate_id = delegate['delegate_id']
101-
mandate_id = mandate['mandate_id']
102-
return f'/v1/representees/{representee_id}/delegates/{delegate_id}/mandates/{mandate_id}/subdelegates'
103-
104-
105-
def set_delete_link(mandate, representee, delegate):
106-
representee_id = representee['representee_id']
107-
delegate_id = delegate['delegate_id']
108-
mandate_id = mandate['mandate_id']
109-
return f'/v1/representees/{representee_id}/delegates/{delegate_id}/mandates/{mandate_id}'

0 commit comments

Comments
 (0)