Skip to content

Commit

Permalink
Redirect without auth
Browse files Browse the repository at this point in the history
  • Loading branch information
nathan-moore-97 committed Jan 30, 2024
1 parent f2ea95d commit 2e0c18f
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 19 deletions.
59 changes: 43 additions & 16 deletions qualtrix/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
qualtrix rest api
"""

from asyncio import create_task
import datetime
from datetime import datetime, timedelta
import logging
import time

import fastapi
from fastapi import HTTPException
Expand All @@ -29,7 +33,10 @@ class SessionModel(SurveyModel):

class RedirectModel(SurveyModel):
targetSurveyId: str
responseId: str
email: str
first_name: str
last_name: str
auth_token: str


@router.post("/bulk-responses")
Expand All @@ -46,26 +53,46 @@ async def get_response(request: ResponseModel):


@router.post("/redirect")
async def get_redirect(request: RedirectModel):
async def intake_redirect(request: RedirectModel):
start_time = time.time()
try:
email = client.get_email(request.surveyId, request.responseId)
contact = client.get_contact(settings.DIRECTORY_ID, email)
distribution = client.get_distribution(settings.DIRECTORY_ID, contact["id"])
return client.get_link(request.targetSurveyId, distribution["distributionId"])
# participant = client.get_participant(request.surveyId, request.responseId)
directory_entry = client.create_directory_entry(
request.email,
request.first_name,
request.last_name,
settings.DIRECTORY_ID,
settings.MAILING_LIST_ID,
)
# TODO: Abstract this into a general create_distribution with a type argument
email_distribution = client.create_email_distribution(
directory_entry["contactLookupId"],
settings.DIRECTORY_ID,
settings.LIBRARY_ID,
settings.INVITE_MESSAGE_ID,
settings.MAILING_LIST_ID,
request.targetSurveyId,
)
link = client.get_link(request.targetSurveyId, email_distribution["id"])

# If link creation succeeds, create reminders while the link is returned
create_task(create_reminder_distributions(email_distribution["id"]))

log.info("Redirect link created in %.2f seconds" % (time.time() - start_time))
return link
except error.QualtricsError as e:
logging.error(e)
raise HTTPException(status_code=400, detail=e.args)
# the next time any client side changes are required update this to 422
raise HTTPException(status_code=422, detail=e.args)


@router.post("/redirect-v2")
async def get_redirect_v2(request: RedirectModel):
try:
participant = client.get_participant(request.surveyId, request.responseId)
return participant
except error.QualtricsError as e:
logging.error(e)
# the next time any client side changes are required update this to 422
raise HTTPException(status_code=400, detail=e.args)
async def create_reminder_distributions(distribution_id: str):
client.create_reminder_distribution(
settings.LIBRARY_ID,
settings.REMINDER_MESSAGE_ID,
distribution_id,
(datetime.utcnow() + timedelta(minutes=1)),
)


@router.post("/survey-schema")
Expand Down
188 changes: 186 additions & 2 deletions qualtrix/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import logging
import requests
import time
import datetime
from datetime import datetime, timedelta


from qualtrix import settings, error

Expand All @@ -14,6 +17,17 @@
auth_header = {"X-API-TOKEN": settings.API_TOKEN}


class Participant:
def __init__(
self, r_id: str, f_name: str, l_name: str, email: str, lang: str
) -> None:
self.response_id = r_id
self.first_name = f_name
self.last_name = l_name
self.email = email
self.language = lang


class IBetaSurveyQuestion(Enum):
TESTER_ID = 1
TEST_TYPE = 2
Expand Down Expand Up @@ -63,7 +77,7 @@ def get_participant(survey_id: str, response_id: str):
header["Accept"] = "application/json"

logging.info(
f"Survey, Response -> Email (SurveyId={survey_id}, Response={response_id})"
f"Survey, Response -> Participant (SurveyId={survey_id}, Response={response_id})"
)

# ResponseId -> Email
Expand All @@ -78,7 +92,177 @@ def get_participant(survey_id: str, response_id: str):
if "error" in response_id_to_participant["meta"]:
raise error.QualtricsError(response_id_to_participant["meta"]["error"])

return response_id_to_participant
participant_str = response_id_to_participant["result"]["values"]
if participant_str is None:
raise error.QualtricsError(
"Participant not found, did they complete the intake survey?"
)

f_name = participant_str["QID37_1"]
l_name = participant_str["QID37_2"]
email = participant_str["QID37_3"]
lang = participant_str["userLanguage"]

return Participant(response_id, f_name, l_name, email, lang)


def create_directory_entry(
email: str, first_name: str, last_name: str, directory_id: str, mailing_list_id: str
):
header = copy.deepcopy(auth_header)
header["Accept"] = "application/json"

logging.info(f"Creating new directory entry for {email}")

directory_payload = {
"firstName": first_name,
"lastName": last_name,
"email": email,
"embeddedData": {
"RulesConsentID": "test-rules",
"Date": "test-date",
"time": "test-time",
"SurveyswapID": "",
"utm_source": "test-utmsource",
"utm_medium": "test-utmmedium",
"utm_campaign": "test-utmcampaign",
},
}

# Create contact
r = requests.post(
settings.BASE_URL
+ f"/directories/{directory_id}/mailinglists/{mailing_list_id}/contacts",
headers=header,
params={"includeEmbedded": "true"},
json=directory_payload,
timeout=settings.TIMEOUT,
)

create_directory_entry_response = r.json()
if "error" in create_directory_entry_response["meta"]:
raise error.QualtricsError(create_directory_entry_response["meta"]["error"])

directory_entry = create_directory_entry_response.get("result", None)
if directory_entry is None:
raise error.QualtricsError("Something went wrong creating the contact")

return directory_entry


def create_reminder_distribution(
library_id: str,
reminder_message_id: str,
distribution_id: str,
reminder_date: datetime,
):

header = copy.deepcopy(auth_header)
header["Accept"] = "application/json"

logging.info(
f"Create reminder distribution for {distribution_id} on {reminder_date}"
)

create_reminder_distribution_payload = {
"message": {"libraryId": library_id, "messageId": reminder_message_id},
"header": {
"fromEmail": settings.FROM_EMAIL,
"replyToEmail": settings.REPLY_TO_EMAIL,
"fromName": settings.FROM_NAME,
"subject": settings.REMINDER_SUBJECT,
},
"embeddedData": {"property1": "string", "property2": "string"},
"sendDate": reminder_date.isoformat() + "Z",
}

r = requests.post(
settings.BASE_URL + f"/distributions/{distribution_id}/reminders",
headers=header,
json=create_reminder_distribution_payload,
timeout=settings.TIMEOUT,
)

create_reminder_distribution_response = r.json()
if "error" in create_reminder_distribution_response["meta"]:
raise error.QualtricsError(
create_reminder_distribution_response["meta"]["error"]
)

reminder_distribution = create_reminder_distribution_response["result"]
if reminder_distribution is None:
raise error.QualtricsError("Something went wrong creating the distribution")


def add_participant_to_contact_list(
auth_token: str, survey_label: str, survey_link: str
):
header = copy.deepcopy(auth_header)
header["Accept"] = "application/json"

logging.info("Add participant to the contact list")

add_particpant_payload = {
"embeddedData": {survey_label: survey_link, "auth_token": auth_token}
}

r = requests.post(
settings.BASE_URL + f"/distributions/{distribution_id}/reminders",
headers=header,
json=create_reminder_distribution_payload,
timeout=settings.TIMEOUT,
)


def create_email_distribution(
contact_id: str,
distribution_id: str,
library_id: str,
message_id: str,
mailing_list_id: str,
survey_id: str,
):
header = copy.deepcopy(auth_header)
header["Accept"] = "application/json"

logging.info(f"Create email distribution ")

calltime = datetime.utcnow()
create_distribution_payload = {
"message": {"libraryId": library_id, "messageId": message_id},
"recipients": {"mailingListId": mailing_list_id, "contactId": contact_id},
"header": {
"fromEmail": settings.FROM_EMAIL,
"replyToEmail": settings.REPLY_TO_EMAIL,
"fromName": settings.FROM_NAME,
"subject": settings.INVITE_SUBJECT,
},
"surveyLink": {
"surveyId": survey_id,
"expirationDate": (calltime + timedelta(minutes=5)).isoformat()
+ "Z", # 1 month
"type": "Individual",
},
"embeddedData": {"": ""}, # for some reason this is required
"sendDate": (calltime + timedelta(seconds=10)).isoformat() + "Z",
}

r = requests.post(
settings.BASE_URL + f"/distributions",
headers=header,
json=create_distribution_payload,
timeout=settings.TIMEOUT,
)

create_distribution_response = r.json()
if "error" in create_distribution_response["meta"]:
raise error.QualtricsError(create_distribution_response["meta"]["error"])

email_distribution = create_distribution_response["result"]
if email_distribution is None:
raise error.QualtricsError("Something went wrong creating the distribution")

return email_distribution


def get_email(survey_id: str, response_id: str):
Expand Down
12 changes: 12 additions & 0 deletions qualtrix/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@

app = fastapi.FastAPI()

origins = ["*"]

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.add_middleware(starlette_prometheus.PrometheusMiddleware)
app.add_route("/metrics/", starlette_prometheus.metrics)

Expand Down
39 changes: 38 additions & 1 deletion qualtrix/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,26 @@

LOG_LEVEL = os.getenv("LOG_LEVEL", logging.getLevelName(logging.INFO))

# Qualtrics API Access
API_TOKEN = None
BASE_URL = None

# Qualtrics API Control
DIRECTORY_ID = None
LIBRARY_ID = None
REMINDER_MESSAGE_ID = None
INVITE_MESSAGE_ID = None
MAILING_LIST_ID = None

# Distribution Content Config
FROM_EMAIL = None
REPLY_TO_EMAIL = None
FROM_NAME = None

INVITE_SUBJECT = None
REMINDER_SUBJECT = None
SURVEY_LINK_TYPE = None


try:
vcap_services = os.getenv("VCAP_SERVICES")
Expand All @@ -32,11 +49,31 @@
API_TOKEN = config["api_token"]
BASE_URL = config["base_url"]
DIRECTORY_ID = config["directory_id"]
LIBRARY_ID = config["library_id"]
REMINDER_MESSAGE_ID = config["reminder_message_id"]
INVITE_MESSAGE_ID = config["invite_message_id"]
MAILING_LIST_ID = config["mailing_list_id"]
FROM_EMAIL = config["from_email"]
REPLY_TO_EMAIL = config["reply_to_email"]
FROM_NAME = config["from_name"]
INVITE_SUBJECT = config["invite_subject"]
REMINDER_SUBJECT = config["reminder_subject"]
SURVEY_LINK_TYPE = config["survey_link_type"]

else:
API_TOKEN = os.getenv("QUALTRIX_API_TOKEN")
BASE_URL = os.getenv("QUALTRIX_BASE_URL")
DIRECTORY_ID = os.getenv("QUALTRIX_DIRECTORY_ID")

LIBRARY_ID = os.getenv("QUALTRIX_LIBRARY_ID")
REMINDER_MESSAGE_ID = os.getenv("QUALTRIX_REMINDER_MESSAGE_ID")
INVITE_MESSAGE_ID = os.getenv("QUALTRIX_INVITE_MESSAGE_ID")
MAILING_LIST_ID = os.getenv("QUALTRIX_MAILING_LIST_ID")
FROM_EMAIL = os.getenv("QUALTRIX_FROM_EMAIL")
REPLY_TO_EMAIL = os.getenv("QUALTRIX_REPLY_TO_EMAIL")
FROM_NAME = os.getenv("QUALTRIX_FROM_NAME")
INVITE_SUBJECT = os.getenv("QUALTRIX_INVITE_SUBJECT")
REMINDER_SUBJECT = os.getenv("QUALTRIX_REMINDER_SUBJECT")
SURVEY_LINK_TYPE = os.getenv("QUALTRIX_SURVEY_LINK_TYPE")

except (json.JSONDecodeError, KeyError, FileNotFoundError) as err:
log.warning("Unable to load credentials from VCAP_SERVICES")
Expand Down

0 comments on commit 2e0c18f

Please sign in to comment.