diff --git a/microsetta_private_api/api/_survey.py b/microsetta_private_api/api/_survey.py index a138df1de..4c3c57fec 100644 --- a/microsetta_private_api/api/_survey.py +++ b/microsetta_private_api/api/_survey.py @@ -14,7 +14,7 @@ from microsetta_private_api.repo.transaction import Transaction from microsetta_private_api.repo.vioscreen_repo import VioscreenRepo from microsetta_private_api.util import vioscreen, myfoodrepo, vue_adapter, \ - polyphenol_ffq + polyphenol_ffq, skin_scoring_app from microsetta_private_api.util.vioscreen import VioscreenAdminAPI from microsetta_private_api.config_manager import SERVER_CONFIG @@ -34,31 +34,50 @@ def read_survey_templates(account_id, source_id, language_tag, token_info): with Transaction() as t: source_repo = SourceRepo(t) source = source_repo.get_source(account_id, source_id) + if source is None: return jsonify(code=404, message="No source found"), 404 + template_repo = SurveyTemplateRepo(t) + if source.source_type == Source.SOURCE_TYPE_HUMAN: - return jsonify([template_repo.get_survey_template_link_info(x) - for x in [ - SurveyTemplateRepo.VIOSCREEN_ID, - SurveyTemplateRepo.POLYPHENOL_FFQ_ID, - SurveyTemplateRepo.SPAIN_FFQ_ID, - SurveyTemplateRepo.BASIC_INFO_ID, - SurveyTemplateRepo.AT_HOME_ID, - SurveyTemplateRepo.LIFESTYLE_ID, - SurveyTemplateRepo.GUT_ID, - SurveyTemplateRepo.GENERAL_HEALTH_ID, - SurveyTemplateRepo.HEALTH_DIAG_ID, - SurveyTemplateRepo.ALLERGIES_ID, - SurveyTemplateRepo.DIET_ID, - SurveyTemplateRepo.DETAILED_DIET_ID, - SurveyTemplateRepo.OTHER_ID - ]]), 200 + # Checking samples to see if any have + # permission to see Skin Scoring App survey + sample_repo = SampleRepo(t) + samples = sample_repo.get_samples_by_source(account_id, source_id) + if samples: + has_skin_sample = any( + SurveyTemplateRepo.SBI_PROJECT_ID + in s.project_id for s in samples + ) + else: + has_skin_sample = False + + template_ids = [ + SurveyTemplateRepo.VIOSCREEN_ID, + SurveyTemplateRepo.POLYPHENOL_FFQ_ID, + SurveyTemplateRepo.SPAIN_FFQ_ID, + SurveyTemplateRepo.BASIC_INFO_ID, + SurveyTemplateRepo.AT_HOME_ID, + SurveyTemplateRepo.LIFESTYLE_ID, + SurveyTemplateRepo.GUT_ID, + SurveyTemplateRepo.GENERAL_HEALTH_ID, + SurveyTemplateRepo.HEALTH_DIAG_ID, + SurveyTemplateRepo.ALLERGIES_ID, + SurveyTemplateRepo.DIET_ID, + SurveyTemplateRepo.DETAILED_DIET_ID, + SurveyTemplateRepo.OTHER_ID + ] + if has_skin_sample: + template_ids.append(SurveyTemplateRepo.SKIN_SCORING_APP_ID) + elif source.source_type == Source.SOURCE_TYPE_ANIMAL: - return jsonify([template_repo.get_survey_template_link_info(x) - for x in [2]]), 200 + template_ids = [2] else: - return jsonify([]), 200 + template_ids = [] + + return jsonify([template_repo.get_survey_template_link_info(x) + for x in template_ids]), 200 def _remote_survey_url_vioscreen(transaction, account_id, source_id, @@ -181,6 +200,25 @@ def _remote_survey_url_spain_ffq(transaction, account_id, source_id): return SERVER_CONFIG['spain_ffq_url'] +def _remote_survey_url_skin_scoring_app(transaction, + account_id, + source_id, + language_tag): + st_repo = SurveyTemplateRepo(transaction) + + skin_scoring_app_id = \ + st_repo.get_skin_scoring_app_id_if_exists(account_id, + source_id) + + if skin_scoring_app_id is None: + skin_scoring_app_id = \ + st_repo.create_skin_scoring_app_entry(account_id, + source_id, + language_tag) + return skin_scoring_app.gen_url(skin_scoring_app_id, + language_tag) + + def read_survey_template(account_id, source_id, survey_template_id, language_tag, token_info, survey_redirect_url=None, vioscreen_ext_sample_id=None, @@ -220,6 +258,12 @@ def read_survey_template(account_id, source_id, survey_template_id, url = _remote_survey_url_spain_ffq(t, account_id, source_id) + elif survey_template_id == \ + SurveyTemplateRepo.SKIN_SCORING_APP_ID: + url = _remote_survey_url_skin_scoring_app(t, + account_id, + source_id, + language_tag) else: raise ValueError(f"Cannot generate URL for survey " f"{survey_template_id}") diff --git a/microsetta_private_api/api/tests/test_integration.py b/microsetta_private_api/api/tests/test_integration.py index 4b6451d94..4ebd2fd5d 100644 --- a/microsetta_private_api/api/tests/test_integration.py +++ b/microsetta_private_api/api/tests/test_integration.py @@ -625,6 +625,42 @@ def test_bobo_takes_polyphenol_ffq(self): ) check_response(resp, 404) + @skipIf(SERVER_CONFIG['skin_scoring_app_url'] in + ('', 'ssa_placeholder'), + "Skin Scoring App secrets not provided") + def test_bobo_takes_skin_scoring_app(self): + bobo = self._bobo_to_claim_a_sample() + + # take Skin Scoring App + resp = self.client.get( + '/api/accounts/%s/sources/%s/survey_templates/10005' + '?language_tag=en_US' % + (ACCT_ID, bobo['source_id']), + headers=MOCK_HEADERS + ) + check_response(resp) + data = json.loads(resp.data) + exp_start = SERVER_CONFIG['skin_scoring_app_url'] + url = data['survey_template_text']['url'] + self.assertTrue(url.startswith(exp_start)) + + # verify we err if we attempt to answer the survey. an "answer" here is + # undefined + resp = self.client.post( + '/api/accounts/%s/sources/%s/surveys' + '?language_tag=en_US' % + (ACCT_ID, bobo['source_id']), + content_type='application/json', + data=json.dumps( + { + "survey_template_id": + SurveyTemplateRepo.SKIN_SCORING_APP_ID, + "survey_text": {'key': 'stuff'} + }), + headers=MOCK_HEADERS + ) + check_response(resp, 404) + @skipIf(SERVER_CONFIG['spain_ffq_url'] in ('', 'sffq_placeholder'), "Spain FFQ secrets not provided") def test_bobo_takes_spain_ffq(self): diff --git a/microsetta_private_api/db/patches/0143.sql b/microsetta_private_api/db/patches/0143.sql new file mode 100644 index 000000000..87859411b --- /dev/null +++ b/microsetta_private_api/db/patches/0143.sql @@ -0,0 +1,13 @@ +CREATE TABLE ag.skin_scoring_app_registry ( + skin_scoring_app_id VARCHAR PRIMARY KEY, + account_id UUID NOT NULL, + source_id UUID, + language_tag VARCHAR, + deleted BOOLEAN NOT NULL DEFAULT false, + creation_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT fk_skin_scoring_app_registry_account FOREIGN KEY (account_id) REFERENCES ag.account(id), + CONSTRAINT fk_skin_scoring_app_registry_source FOREIGN KEY (source_id) REFERENCES ag.source(id) +); + +CREATE INDEX skin_scoring_app_registry_source ON ag.skin_scoring_app_registry (account_id, source_id); \ No newline at end of file diff --git a/microsetta_private_api/repo/sample_repo.py b/microsetta_private_api/repo/sample_repo.py index 64c361b87..a08276f71 100644 --- a/microsetta_private_api/repo/sample_repo.py +++ b/microsetta_private_api/repo/sample_repo.py @@ -258,6 +258,9 @@ def get_samples_by_source(self, account_id, source_id, sample.kit_id = self._get_supplied_kit_id_by_sample( sample.barcode ) + sample.project_id = self._get_project_ids_by_sample( + sample.barcode + ) samples.append(sample) return samples @@ -404,6 +407,19 @@ def _get_supplied_kit_id_by_sample(self, sample_barcode): row = cur.fetchone() return row[0] + def _get_project_ids_by_sample(self, sample_barcode): + with self._transaction.cursor() as cur: + cur.execute( + "SELECT project_id " + "FROM barcodes.project_barcode " + "WHERE barcode = %s", + (sample_barcode, ) + ) + rows = cur.fetchall() + + project_ids = [row[0] for row in rows] + return project_ids + def scrub(self, account_id, source_id, sample_id): """Wipe out free text information for a sample diff --git a/microsetta_private_api/repo/survey_template_repo.py b/microsetta_private_api/repo/survey_template_repo.py index fc1f2a511..a8d906739 100644 --- a/microsetta_private_api/repo/survey_template_repo.py +++ b/microsetta_private_api/repo/survey_template_repo.py @@ -1,3 +1,6 @@ +import random +import string +import psycopg2 from werkzeug.exceptions import NotFound from microsetta_private_api.config_manager import SERVER_CONFIG @@ -23,6 +26,7 @@ class SurveyTemplateRepo(BaseRepo): MYFOODREPO_ID = 10002 POLYPHENOL_FFQ_ID = 10003 SPAIN_FFQ_ID = 10004 + SKIN_SCORING_APP_ID = 10005 BASIC_INFO_ID = 10 AT_HOME_ID = 11 LIFESTYLE_ID = 12 @@ -36,6 +40,7 @@ class SurveyTemplateRepo(BaseRepo): SURFERS_ID = 20 COVID19_ID = 21 OTHER_ID = 22 + SBI_PROJECT_ID = 58 SURVEY_INFO = { # For now, let's keep legacy survey info as well. @@ -105,6 +110,12 @@ class SurveyTemplateRepo(BaseRepo): "1.0", "remote" ), + SKIN_SCORING_APP_ID: SurveyTemplateLinkInfo( + SKIN_SCORING_APP_ID, + "Skin Scoring App", + "1.0", + "remote" + ), BASIC_INFO_ID: SurveyTemplateLinkInfo( BASIC_INFO_ID, "Basic Information", @@ -755,6 +766,86 @@ def delete_spain_ffq(self, account_id, source_id): AND source_id=%s""", (account_id, source_id)) + def create_skin_scoring_app_entry(self, + account_id, + source_id, + language_tag,): + """Return a newly created Skin Scoring App ID + + Parameters + ---------- + account_id : str, UUID + The account UUID + source_id : str, UUID + The source UUID + language_tag: str + The user's language tag + + Returns + ------- + str + The newly created Skin Scoring App ID + """ + characters = string.ascii_lowercase + string.digits + + while True: + skin_scoring_app_id = ''.join(random.choices(characters, k=8)) + + try: + with self._transaction.cursor() as cur: + cur.execute("""INSERT INTO ag.skin_scoring_app_registry + (skin_scoring_app_id, account_id, + source_id, language_tag) + VALUES (%s, %s, %s, %s)""", + (skin_scoring_app_id, account_id, + source_id, language_tag)) + + # Put a survey into ag_login_surveys + cur.execute("INSERT INTO ag_login_surveys(" + "ag_login_id, " + "survey_id, " + "vioscreen_status, " + "source_id, " + "survey_template_id) " + "VALUES(%s, %s, %s, %s, %s)", + (account_id, skin_scoring_app_id, None, + source_id, + SurveyTemplateRepo.SKIN_SCORING_APP_ID)) + + return skin_scoring_app_id + except psycopg2.IntegrityError: + self._transaction.rollback() + + def get_skin_scoring_app_id_if_exists(self, + account_id, + source_id): + """Return a Skin Scoring App ID if one exists + + Parameters + ---------- + account_id : str, UUID + The account UUID + source_id : str, UUID + The source UUID + + Returns + ------- + (str) or (None) + The associated Skin Scoring App ID + It's impossible to find one without the other + """ + with self._transaction.cursor() as cur: + cur.execute("""SELECT skin_scoring_app_id + FROM ag.skin_scoring_app_registry + WHERE account_id=%s AND source_id=%s""", + (account_id, source_id)) + res = cur.fetchone() + + if res is None: + return None + else: + return res[0] + def get_vioscreen_sample_to_user(self): """Obtain a mapping of sample barcode to vioscreen user""" with self._transaction.cursor() as cur: @@ -1127,6 +1218,7 @@ def has_external_surveys(self, account_id, source_id): getters = (self.get_myfoodrepo_id_if_exists, self.get_polyphenol_ffq_id_if_exists, self.get_spain_ffq_id_if_exists, + self.get_skin_scoring_app_id_if_exists, self.get_vioscreen_all_ids_if_exists) for get in getters: @@ -1306,7 +1398,8 @@ def _generate_empty_survey(self, survey_template_id, return_tuple=False): if survey_template_id in [self.VIOSCREEN_ID, self.MYFOODREPO_ID, self.POLYPHENOL_FFQ_ID, - self.SPAIN_FFQ_ID]: + self.SPAIN_FFQ_ID, + self.SKIN_SCORING_APP_ID]: raise ValueError("survey_template_id must be for a local " "survey") else: diff --git a/microsetta_private_api/repo/tests/test_sample.py b/microsetta_private_api/repo/tests/test_sample.py index e917ed439..5793033d1 100644 --- a/microsetta_private_api/repo/tests/test_sample.py +++ b/microsetta_private_api/repo/tests/test_sample.py @@ -168,6 +168,30 @@ def test_get_supplied_kit_id_by_sample(self): ) self.assertEqual(kit_id, supplied_kit_id) + def test_get_project_ids_by_sample(self): + with Transaction() as t: + # First we'll create a kit so that we have a kit_id/barcode combo + # to test with + admin_repo = AdminRepo(t) + res = admin_repo.create_kits( + 1, + 1, + "UNITTEST", + [1, 2] + ) + + # Extract the info from results. We know we created 1 kit with 1 + # sample so we don't need to iterate + kit_info = res['created'][0] + sample_barcode = kit_info['sample_barcodes'][0] + + # Verify that the function returns the correct project_id + sample_repo = SampleRepo(t) + project_ids = sample_repo._get_project_ids_by_sample( + sample_barcode + ) + self.assertEqual([1, 2], project_ids) + if __name__ == '__main__': unittest.main() diff --git a/microsetta_private_api/repo/tests/test_survey_template_repo.py b/microsetta_private_api/repo/tests/test_survey_template_repo.py index 1ed86cfc1..94bb6bd7d 100644 --- a/microsetta_private_api/repo/tests/test_survey_template_repo.py +++ b/microsetta_private_api/repo/tests/test_survey_template_repo.py @@ -395,6 +395,41 @@ def test_get_spain_ffq_id_if_exists_false(self): TEST1_SOURCE_ID) self.assertEqual(obs, None) + def test_create_skin_scoring_app_entry_valid(self): + with Transaction() as t: + template_repo = SurveyTemplateRepo(t) + obs = template_repo.create_skin_scoring_app_entry(TEST1_ACCOUNT_ID, + TEST1_SOURCE_ID, + 'en_US') + self.assertEqual(len(obs), 8) + + def test_create_skin_scoring_app_entry_invalid(self): + with Transaction() as t: + template_repo = SurveyTemplateRepo(t) + with self.assertRaises(InvalidTextRepresentation): + template_repo.create_skin_scoring_app_entry('', + TEST1_SOURCE_ID, + 'en_US') + + def test_get_skin_scoring_app_id_if_exists_true(self): + with Transaction() as t: + template_repo = SurveyTemplateRepo(t) + test_ssa_id = template_repo.create_skin_scoring_app_entry( + TEST1_ACCOUNT_ID, TEST1_SOURCE_ID, 'en_US' + ) + obs = template_repo.get_skin_scoring_app_id_if_exists( + TEST1_ACCOUNT_ID, TEST1_SOURCE_ID + ) + self.assertEqual(test_ssa_id, obs) + + def test_get_skin_scoring_app_id_if_exists_false(self): + with Transaction() as t: + template_repo = SurveyTemplateRepo(t) + obs = template_repo.get_skin_scoring_app_id_if_exists( + TEST1_ACCOUNT_ID, TEST1_SOURCE_ID + ) + self.assertEqual(obs, None) + def test_create_vioscreen_id_valid(self): with Transaction() as t: template_repo = SurveyTemplateRepo(t) diff --git a/microsetta_private_api/server_config.json b/microsetta_private_api/server_config.json index d49f8a7bd..2f7cc0420 100644 --- a/microsetta_private_api/server_config.json +++ b/microsetta_private_api/server_config.json @@ -42,6 +42,7 @@ "fundrazr_organization": "fundrazr_org_placeholder", "polyphenol_ffq_url": "pffq_placeholder", "spain_ffq_url": "sffq_placeholder", + "skin_scoring_app_url": "ssa_placeholder", "fulfillment_account_id": "000fc4cd-8fa4-db8b-e050-8a800c5d81b7", "google_geocoding_url": "https://maps.googleapis.com/maps/api/geocode/json", "google_geocoding_key": "geocoding_key_placeholder", diff --git a/microsetta_private_api/util/skin_scoring_app.py b/microsetta_private_api/util/skin_scoring_app.py new file mode 100644 index 000000000..77eeb0841 --- /dev/null +++ b/microsetta_private_api/util/skin_scoring_app.py @@ -0,0 +1,11 @@ +from microsetta_private_api.config_manager import SERVER_CONFIG + + +def gen_url(skin_scoring_app_id, language_tag): + language_tag = language_tag.lower() + + url = SERVER_CONFIG['skin_scoring_app_url'] + url += f"?yid={skin_scoring_app_id}" + url += f"&country={language_tag}" + + return url