/// <summary> /// Receive an SMS message. /// </summary> public async Task <APIGatewayProxyResponse> ReceiveSmsAsync(APIGatewayProxyRequest request, ILambdaContext context) { var logger = context.Logger; logger.LogLine("Receiving SMS"); // API Gateway isn't nice like MVC. We need to parse the form ourselves. var formValues = GetFormValues(request); if (!TwilioRequestValidator.IsValidPostRequest(request, formValues)) { throw new PreTalkSurveyException("Unable to validate signature on request"); } // Get the SMS message from the form and make sure it comes from the expected phone number. var sms = GetSmsMessage(formValues); if (sms.To != ServerlessConfig.TwilioPhoneNumber) { throw new PreTalkSurveyException("Invalid incoming phone number"); } // figure out what to do with the incoming message. try { var surveyResponse = await _DataAccess.GetSurveyResponseAsync(Sanitizer.HashPhoneNumber(sms.From)).ConfigureAwait(false); if (surveyResponse == null) { // if we've never seen this phone number before, it's the start of a new survey logger.LogLine($"New phone number {Sanitizer.MaskPhoneNumber(sms.From)}, starting new survey"); await StartNewSurveyAsync(sms.From, logger).ConfigureAwait(false); } else if (!string.IsNullOrEmpty(surveyResponse.TaskToken)) { if (EqualsIgnoreCase(sms.Body, "hello") || EqualsIgnoreCase(sms.Body, "start")) { // if they send "hello" or "start", terminate the workflow and start it again logger.LogLine($"Restarting workflow for {Sanitizer.MaskPhoneNumber(sms.From)}"); await FailWaitForResponseAsync(surveyResponse.TaskToken).ConfigureAwait(false); await StartNewSurveyAsync(sms.From, logger).ConfigureAwait(false); } // if there's a task token, we need to advance the state machine var taskToken = surveyResponse.TaskToken; var state = surveyResponse.SavedState; // evaluate the answer to the question var questionResponse = Survey.EvaluateResponseToQuestion(state.Question, sms.Body.Trim()); state.IsValidResponse = questionResponse.IsValidResponse; logger.LogLine($"Evaluated response to question \"{state.Question}\". Response \"{sms.Body}\" from {Sanitizer.MaskPhoneNumber(sms.From)}. IsValidResponse={questionResponse.IsValidResponse};StandardizedResponse={questionResponse.StandardizedResponse}"); // clear the token and the saved state -- we want to make sure these values aren't reused // so we save before completing the activity. logger.LogLine($"Clearing state for {Sanitizer.MaskPhoneNumber(sms.From)}"); surveyResponse.TaskToken = ""; surveyResponse.SavedState = null; // store the answer if (state.IsValidResponse) { surveyResponse.Responses[questionResponse.Question] = questionResponse.StandardizedResponse; } await _DataAccess.SaveSurveyResponeAsync(surveyResponse); // store the answer to the question // advance the state machine logger.LogLine($"Completing response for task token {taskToken}"); await CompleteWaitForResponseAsync(taskToken, state).ConfigureAwait(false); } else { // they must have previously completed the survey - start again logger.LogLine($"Restarting workflow for {Sanitizer.MaskPhoneNumber(sms.From)} - previously completed"); await StartNewSurveyAsync(sms.From, logger).ConfigureAwait(false); } } catch { logger.LogLine($"Sending snarky error message to {Sanitizer.MaskPhoneNumber(sms.From)}"); await SendErrorMessage(sms.From).ConfigureAwait(false); throw; } logger.LogLine("Returning OK response"); return(GetOkResponse()); }
public async Task <State> ErrorQuestionXAsync(int questionNumber, State state, ILambdaContext context) { await SendSms(state.PhoneNumber, Survey.GetErrorMessage(questionNumber)); return(state); }