Skip to content

Commit 12df590

Browse files
Merge branch 'master' into csymons_skin_surveys
2 parents 4963bad + 61d7e1b commit 12df590

13 files changed

+853
-51
lines changed

microsetta_private_api/api/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
read_survey_template, read_survey_templates, read_answered_survey,
2626
read_answered_surveys, submit_answered_survey,
2727
read_answered_survey_associations, top_food_report,
28-
read_myfoodrepo_available_slots
28+
read_myfoodrepo_available_slots, get_skin_scoring_app_credentials,
29+
post_skin_scoring_app_credentials
2930
)
3031
from ._sample import (
3132
read_sample_association, associate_sample, read_sample_associations,
@@ -103,6 +104,8 @@
103104
'read_answered_survey_associations',
104105
'top_food_report',
105106
'read_myfoodrepo_available_slots',
107+
'get_skin_scoring_app_credentials',
108+
'post_skin_scoring_app_credentials',
106109
'read_sample_association',
107110
'associate_sample',
108111
'read_sample_associations',

microsetta_private_api/api/_survey.py

+107-21
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,41 @@ def read_survey_templates(account_id, source_id, language_tag, token_info):
3434
with Transaction() as t:
3535
source_repo = SourceRepo(t)
3636
source = source_repo.get_source(account_id, source_id)
37+
3738
if source is None:
3839
return jsonify(code=404, message="No source found"), 404
40+
3941
template_repo = SurveyTemplateRepo(t)
42+
4043
if source.source_type == Source.SOURCE_TYPE_HUMAN:
41-
return jsonify([template_repo.get_survey_template_link_info(x)
42-
for x in [
43-
SurveyTemplateRepo.VIOSCREEN_ID,
44-
SurveyTemplateRepo.POLYPHENOL_FFQ_ID,
45-
SurveyTemplateRepo.SPAIN_FFQ_ID,
46-
SurveyTemplateRepo.BASIC_INFO_ID,
47-
SurveyTemplateRepo.AT_HOME_ID,
48-
SurveyTemplateRepo.LIFESTYLE_ID,
49-
SurveyTemplateRepo.GUT_ID,
50-
SurveyTemplateRepo.GENERAL_HEALTH_ID,
51-
SurveyTemplateRepo.HEALTH_DIAG_ID,
52-
SurveyTemplateRepo.ALLERGIES_ID,
53-
SurveyTemplateRepo.DIET_ID,
54-
SurveyTemplateRepo.DETAILED_DIET_ID,
55-
SurveyTemplateRepo.SKIN_ID,
56-
SurveyTemplateRepo.SKIN_HEALTH_DIAGNOSIS_ID,
57-
SurveyTemplateRepo.OTHER_ID
58-
]]), 200
44+
template_ids = [
45+
SurveyTemplateRepo.VIOSCREEN_ID,
46+
SurveyTemplateRepo.POLYPHENOL_FFQ_ID,
47+
SurveyTemplateRepo.SPAIN_FFQ_ID,
48+
SurveyTemplateRepo.BASIC_INFO_ID,
49+
SurveyTemplateRepo.AT_HOME_ID,
50+
SurveyTemplateRepo.LIFESTYLE_ID,
51+
SurveyTemplateRepo.GUT_ID,
52+
SurveyTemplateRepo.GENERAL_HEALTH_ID,
53+
SurveyTemplateRepo.HEALTH_DIAG_ID,
54+
SurveyTemplateRepo.ALLERGIES_ID,
55+
SurveyTemplateRepo.DIET_ID,
56+
SurveyTemplateRepo.DETAILED_DIET_ID,
57+
SurveyTemplateRepo.SKIN_ID,
58+
SurveyTemplateRepo.SKIN_HEALTH_DIAGNOSIS_ID,
59+
SurveyTemplateRepo.OTHER_ID
60+
]
61+
if template_repo.check_display_skin_scoring_app(
62+
account_id, source_id
63+
):
64+
template_ids.append(SurveyTemplateRepo.SKIN_SCORING_APP_ID)
5965
elif source.source_type == Source.SOURCE_TYPE_ANIMAL:
60-
return jsonify([template_repo.get_survey_template_link_info(x)
61-
for x in [2]]), 200
66+
template_ids = [2]
6267
else:
63-
return jsonify([]), 200
68+
template_ids = []
69+
70+
return jsonify([template_repo.get_survey_template_link_info(x)
71+
for x in template_ids]), 200
6472

6573

6674
def _remote_survey_url_vioscreen(transaction, account_id, source_id,
@@ -183,6 +191,23 @@ def _remote_survey_url_spain_ffq(transaction, account_id, source_id):
183191
return SERVER_CONFIG['spain_ffq_url']
184192

185193

194+
def _remote_survey_url_skin_scoring_app(transaction,
195+
account_id,
196+
source_id):
197+
st_repo = SurveyTemplateRepo(transaction)
198+
199+
# Confirm that the user has credentials allocated
200+
ssa_u, _ = st_repo.get_skin_scoring_app_credentials_if_exists(
201+
account_id,
202+
source_id
203+
)
204+
205+
if ssa_u is None:
206+
raise NotFound("Sorry, you were not allocated credentials")
207+
208+
return SERVER_CONFIG['skin_app_url']
209+
210+
186211
def read_survey_template(account_id, source_id, survey_template_id,
187212
language_tag, token_info, survey_redirect_url=None,
188213
vioscreen_ext_sample_id=None,
@@ -222,6 +247,11 @@ def read_survey_template(account_id, source_id, survey_template_id,
222247
url = _remote_survey_url_spain_ffq(t,
223248
account_id,
224249
source_id)
250+
elif survey_template_id == \
251+
SurveyTemplateRepo.SKIN_SCORING_APP_ID:
252+
url = _remote_survey_url_skin_scoring_app(t,
253+
account_id,
254+
source_id)
225255
else:
226256
raise ValueError(f"Cannot generate URL for survey "
227257
f"{survey_template_id}")
@@ -501,3 +531,59 @@ def read_myfoodrepo_available_slots():
501531
resp = jsonify(code=200, number_of_available_slots=available,
502532
total_number_of_slots=total)
503533
return resp, 200
534+
535+
536+
def get_skin_scoring_app_credentials(account_id, source_id, token_info):
537+
_validate_account_access(token_info, account_id)
538+
539+
with Transaction() as t:
540+
st_repo = SurveyTemplateRepo(t)
541+
ssa_u, ssa_s = st_repo.get_skin_scoring_app_credentials_if_exists(
542+
account_id, source_id
543+
)
544+
response_obj = {
545+
"app_username": ssa_u,
546+
"app_studycode": ssa_s
547+
}
548+
return jsonify(response_obj), 200
549+
550+
551+
def post_skin_scoring_app_credentials(account_id, source_id, token_info):
552+
_validate_account_access(token_info, account_id)
553+
554+
with Transaction() as t:
555+
st_repo = SurveyTemplateRepo(t)
556+
557+
# First, confirm that the source doesn't already have credentials
558+
ssa_u, _ = st_repo.get_skin_scoring_app_credentials_if_exists(
559+
account_id, source_id
560+
)
561+
562+
# This shouldn't happen, but if it does, return an error
563+
if ssa_u is not None:
564+
return jsonify(
565+
code=400,
566+
message="Credentials already exist"
567+
), 400
568+
569+
# Now, try to allocate credentials and create an entry in the skin
570+
# scoring app registry table
571+
ssa_u, ssa_s = st_repo.create_skin_scoring_app_entry(
572+
account_id, source_id
573+
)
574+
t.commit()
575+
576+
if ssa_u is None:
577+
# No credentials were available
578+
return jsonify(
579+
code=404,
580+
message="No credentials available"
581+
), 404
582+
else:
583+
# Credentials were successfully allocated
584+
return jsonify(
585+
{
586+
"app_username": ssa_u,
587+
"app_studycode": ssa_s
588+
}
589+
), 201

microsetta_private_api/api/microsetta_private_api.yaml

+58
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,64 @@ paths:
10241024
'404':
10251025
$ref: '#/components/responses/404NotFound'
10261026

1027+
'/accounts/{account_id}/sources/{source_id}/surveys/skin_scoring_app_credentials':
1028+
get:
1029+
operationId: microsetta_private_api.api.get_skin_scoring_app_credentials
1030+
tags:
1031+
- Surveys (By Source)
1032+
summary: Get skin scoring app credentials associated with source, if they exist
1033+
description: Get skin scoring app credentials associated with source, if they exist
1034+
parameters:
1035+
- $ref: '#/components/parameters/account_id'
1036+
- $ref: '#/components/parameters/source_id'
1037+
responses:
1038+
'200':
1039+
description: Credentials for skin scoring app
1040+
content:
1041+
application/json:
1042+
schema:
1043+
type: object
1044+
properties:
1045+
app_username:
1046+
type: string
1047+
nullable: true
1048+
app_studycode:
1049+
type: string
1050+
nullable: true
1051+
'401':
1052+
$ref: '#/components/responses/401Unauthorized'
1053+
'403':
1054+
$ref: '#/components/responses/403Forbidden'
1055+
post:
1056+
operationId: microsetta_private_api.api.post_skin_scoring_app_credentials
1057+
tags:
1058+
- Surveys (By Source)
1059+
summary: Create association between a set of skin scoring app credentials and a source
1060+
description: Create association between a set of skin scoring app credentials and a source
1061+
parameters:
1062+
- $ref: '#/components/parameters/account_id'
1063+
- $ref: '#/components/parameters/source_id'
1064+
responses:
1065+
'201':
1066+
description: Credentials for skin scoring app
1067+
content:
1068+
application/json:
1069+
schema:
1070+
type: object
1071+
properties:
1072+
app_username:
1073+
type: string
1074+
app_studycode:
1075+
type: string
1076+
'400':
1077+
description: 'Credentials already exist for source'
1078+
'401':
1079+
$ref: '#/components/responses/401Unauthorized'
1080+
'403':
1081+
$ref: '#/components/responses/403Forbidden'
1082+
'404':
1083+
$ref: '#/components/responses/404NotFound'
1084+
10271085
'/accounts/{account_id}/sources/{source_id}/samples':
10281086
get:
10291087
operationId: microsetta_private_api.api.read_sample_associations

microsetta_private_api/api/tests/test_api.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@
181181
'sample_projects': ['American Gut Project'],
182182
'account_id': None,
183183
'source_id': None,
184-
'sample_site': None}
184+
'sample_site': None,
185+
'sample_project_ids': [1]}
185186

186187
DUMMY_FILLED_SAMPLE_INFO = {
187188
'sample_barcode': BARCODE,
@@ -195,7 +196,8 @@
195196
'sample_projects': ['American Gut Project'],
196197
'account_id': 'foobar',
197198
'source_id': 'foobarbaz',
198-
'sample_site': 'Saliva'}
199+
'sample_site': 'Saliva',
200+
'sample_project_ids': [1]}
199201

200202
ACCT_ID_KEY = "account_id"
201203
ACCT_TYPE_KEY = "account_type"
@@ -594,7 +596,8 @@ def create_dummy_sample_objects(filled=False):
594596
info_dict['account_id'],
595597
None,
596598
info_dict["sample_projects"],
597-
None)
599+
None,
600+
sample_project_ids=info_dict["sample_project_ids"])
598601

599602
return sample_info, sample
600603
# endregion help methods
@@ -2244,6 +2247,10 @@ def test_associate_sample_to_source_success(self):
22442247
exp['account_id'] = ACCT_ID_1
22452248
exp['kit_id'] = None
22462249

2250+
# Remove the sample_project_ids element since we don't expect that
2251+
# to come out of the API
2252+
exp.pop("sample_project_ids")
2253+
22472254
self.assertEqual(get_resp_obj, [exp])
22482255

22492256
# TODO: We should also have tests of associating a sample to a source

microsetta_private_api/db/migration_support.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,35 @@ def migrate_133(TRN):
795795
print("No mapping: " + ffq_id + " - " + barcode)
796796
TRN.execute()
797797

798+
@staticmethod
799+
def migrate_144(TRN):
800+
# We need to load the credentials that the vendor provided in CSV form
801+
# Format is username, studycode and includes a header row
802+
skin_app_credentials_path = SERVER_CONFIG["skin_app_credentials_path"]
803+
if not os.path.exists(skin_app_credentials_path):
804+
print(
805+
"Credentials for app not found:" + skin_app_credentials_path
806+
)
807+
return
808+
809+
with open(skin_app_credentials_path) as csv_file:
810+
csv_contents = csv.reader(csv_file)
811+
header = True
812+
813+
for csv_row in csv_contents:
814+
if header:
815+
header = False
816+
continue
817+
app_username, app_studycode = csv_row
818+
819+
TRN.add(
820+
"INSERT INTO ag.skin_scoring_app_credentials "
821+
"(app_username, app_studycode) "
822+
"VALUES (%s, %s)",
823+
(app_username, app_studycode)
824+
)
825+
TRN.execute()
826+
798827
MIGRATION_LOOKUP = {
799828
"0048.sql": migrate_48.__func__,
800829
"0050.sql": migrate_50.__func__,
@@ -806,7 +835,8 @@ def migrate_133(TRN):
806835
# "0082.sql": migrate_82.__func__
807836
# ...
808837
"0096.sql": migrate_96.__func__,
809-
"0133.sql": migrate_133.__func__
838+
"0133.sql": migrate_133.__func__,
839+
"0144.sql": migrate_144.__func__
810840
}
811841

812842
@classmethod
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-- The organization that hosts the skin-scoring app provides us with username
2+
-- and studycode pairings for participants to access the app. We need to store
3+
-- these pairings, as well as a flag for whether the pairing has been
4+
-- allocated to a participant. We explicitly store this flag to avoid reuse
5+
-- if sources (and their related survey databsase records) were to be deleted.
6+
CREATE TABLE ag.skin_scoring_app_credentials (
7+
app_username VARCHAR PRIMARY KEY,
8+
app_studycode VARCHAR NOT NULL,
9+
credentials_allocated BOOLEAN NOT NULL DEFAULT FALSE
10+
);
11+
12+
-- And we create a registry table, similar to all of the other external
13+
-- surveys we've hosted in the past, to link the username to the account and
14+
-- source that used it.
15+
CREATE TABLE ag.skin_scoring_app_registry (
16+
app_username VARCHAR PRIMARY KEY,
17+
account_id UUID NOT NULL,
18+
source_id UUID,
19+
deleted BOOLEAN NOT NULL DEFAULT false,
20+
creation_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
21+
22+
CONSTRAINT fk_skin_scoring_app_username FOREIGN KEY (app_username) REFERENCES ag.skin_scoring_app_credentials(app_username),
23+
CONSTRAINT fk_skin_scoring_app_registry_account FOREIGN KEY (account_id) REFERENCES ag.account(id),
24+
CONSTRAINT fk_skin_scoring_app_registry_source FOREIGN KEY (source_id) REFERENCES ag.source(id)
25+
);
26+
27+
CREATE INDEX skin_scoring_app_registry_source ON ag.skin_scoring_app_registry (account_id, source_id);

0 commit comments

Comments
 (0)