/// <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());
        }
Example #2
0
        public async Task <State> ErrorQuestionXAsync(int questionNumber, State state, ILambdaContext context)
        {
            await SendSms(state.PhoneNumber, Survey.GetErrorMessage(questionNumber));

            return(state);
        }