AWSTemplateFormatVersion: 2010-09-09 Description: AppSync setup Parameters: FunctionStack: Description: The stack name that deploys the Lambda functions to invoke. Type: String Resources: DataSourceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: appsync.amazonaws.com Policies: - PolicyName: LambdaInvoker PolicyDocument: Version: 2012-10-17 Statement: - Action: lambda:InvokeFunction Effect: Allow Resource: - Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-AccessTokenWrapper' - Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-AccessTokenWrapper' - ':*' - Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-Configuration' - Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-Configuration' - ':*' - Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-Feedback' - Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-Feedback' - ':*' RoleName: !Sub '${AWS::StackName}-lambda-role' GqlApi: Type: AWS::AppSync::GraphQLApi Properties: AuthenticationType: AWS_IAM Name: !Ref AWS::StackName GqlSchema: Type: AWS::AppSync::GraphQLSchema Properties: ApiId: !GetAtt GqlApi.ApiId Definition: | type Mutation { postAccessTokenWrapper(cognitoIdentityId__: String): AccessTokenWrapper! postFeedback(feedback: FeedbackInput! cognitoIdentityId__: String): ! } type Query { getConfiguration(cognitoIdentityId__: String): Configuration } input AccessTokenWrapperInput { accessToken: String } type AccessTokenWrapper { accessToken: String! } input ComponentCheckboxInput { description: String id: Int index: Int label: String options: [String] required: Boolean uniqueName: String } type ComponentCheckbox { description: String id: Int! index: Int! label: String! options: [String]! required: Boolean! uniqueName: String! } input ComponentDropdownInput { description: String id: Int index: Int label: String options: [String] placeholder: String required: Boolean uniqueName: String } type ComponentDropdown { description: String id: Int! index: Int! label: String! options: [String]! placeholder: String! required: Boolean! uniqueName: String! } input ComponentGradingInput { description: String id: Int index: Int label: String ratingViewType: ratingViewType required: Boolean uniqueName: String } type ComponentGrading { description: String id: Int! index: Int! label: String! ratingViewType: ratingViewType! required: Boolean! uniqueName: String! } input ComponentGrading0to10Input { description: String id: Int index: Int label: String ratingViewType: ratingViewType required: Boolean uniqueName: String } type ComponentGrading0to10 { description: String id: Int! index: Int! label: String! ratingViewType: ratingViewType! required: Boolean! uniqueName: String! } input ComponentGrading1to10Input { description: String id: Int index: Int label: String ratingViewType: ratingViewType required: Boolean uniqueName: String } type ComponentGrading1to10 { description: String id: Int! index: Int! label: String! ratingViewType: ratingViewType! required: Boolean! uniqueName: String! } input ComponentImageInput { id: Int imageObject: imageInput index: Int uniqueName: String uploadedResource: imageInput } type ComponentImage { id: Int! imageObject: image index: Int! uniqueName: String! uploadedResource: image } input ComponentLabelInput { id: Int index: Int labelContent: String numOfLines: Int uniqueName: String } type ComponentLabel { id: Int! index: Int! labelContent: String numOfLines: Int! uniqueName: String! } input ComponentNpsInput { description: String id: Int index: Int label: String ratingViewType: ratingViewType required: Boolean uniqueName: String } type ComponentNps { description: String id: Int! index: Int! label: String! ratingViewType: ratingViewType! required: Boolean! uniqueName: String! } input ComponentRadioSelectorInput { description: String id: Int index: Int label: String options: [String] required: Boolean uniqueName: String } type ComponentRadioSelector { description: String id: Int! index: Int! label: String! options: [String]! required: Boolean! uniqueName: String! } input ComponentTextAreaInput { description: String id: Int index: Int label: String numOfLines: Int placeholder: String required: Boolean uniqueName: String } type ComponentTextArea { description: String id: Int! index: Int! label: String! numOfLines: Int! placeholder: String! required: Boolean! uniqueName: String! } input ComponentTextInputInput { description: String id: Int index: Int label: String maxChars: Int placeholder: String required: Boolean uniqueName: String } type ComponentTextInput { description: String id: Int! index: Int! label: String! maxChars: Int! placeholder: String! required: Boolean! uniqueName: String! } input ConfigurationInput { feedbackSubmitUrl: String forms: [formInput] propertyId: Int } type Configuration { feedbackSubmitUrl: String! forms: [form]! propertyId: Int! } input DynamicDataInput { component: String description: String id: Int imageObject: imageInput index: Int label: String options: [String] placeholder: String required: Boolean uniqueName: String } type DynamicData { component: String! description: String id: Int! imageObject: image index: Int! label: String! options: [String] placeholder: String! required: Boolean! uniqueName: String! } input FeedbackInput { dynamicData: feedback_dataInput formId: Int } type Feedback { dynamicData: feedback_data! formId: Int! } input FeedbackCheckboxInput { id: Int index: Int type: String uniqueName: String value: [Int] } type FeedbackCheckbox { id: Int! index: Int! type: String! uniqueName: String! value: [Int]! } input FeedbackDataInput { customParams: AWSJSON pages: [feedback_pageInput] } type FeedbackData { customParams: AWSJSON pages: [feedback_page]! } input FeedbackDropdownInput { id: Int index: Int type: String uniqueName: String value: Int } type FeedbackDropdown { id: Int! index: Int! type: String! uniqueName: String! value: Int! } input FeedbackGradingInput { id: Int index: Int type: String uniqueName: String value: Int } type FeedbackGrading { id: Int! index: Int! type: String! uniqueName: String! value: Int! } input FeedbackGrading0to10Input { id: Int index: Int type: String uniqueName: String value: Int } type FeedbackGrading0to10 { id: Int! index: Int! type: String! uniqueName: String! value: Int! } input FeedbackGrading1to10Input { id: Int index: Int type: String uniqueName: String value: Int } type FeedbackGrading1to10 { id: Int! index: Int! type: String! uniqueName: String! value: Int! } input FeedbackNpsInput { id: Int index: Int type: String uniqueName: String value: Int } type FeedbackNps { id: Int! index: Int! type: String! uniqueName: String! value: Int! } input FeedbackPageInput { components: [] } type FeedbackPage { components: []! } input FeedbackRadioSelectorInput { id: Int index: Int type: String uniqueName: String value: Int } type FeedbackRadioSelector { id: Int! index: Int! type: String! uniqueName: String! value: Int! } input FeedbackTextAreaInput { id: Int index: Int type: String uniqueName: String value: String } type FeedbackTextArea { id: Int! index: Int! type: String! uniqueName: String! value: String! } input FeedbackTextInputInput { id: Int index: Int type: String uniqueName: String value: String } type FeedbackTextInput { id: Int! index: Int! type: String! uniqueName: String! value: String! } input FormInput { formId: Int formJson: formJsonInput formType: formType inviteData: inviteDataInput name: String title: String triggerData: triggerDataInput } type Form { formId: Int! formJson: formJson! formType: formType! inviteData: inviteData name: String! title: String! triggerData: triggerData } input FormJsonInput { pages: [pageInput] } type FormJson { pages: [page]! } input ImageInput { name: String url: String } type Image { name: String! url: String! } input InviteDataInput { declineButtonText: String invitationHeadline: String invitationText: String laterButtonText: String provideButtonText: String } type InviteData { declineButtonText: String! invitationHeadline: String! invitationText: String! laterButtonText: String! provideButtonText: String! } input NumberOfAppSessionInput { compareString: compareString number: String } type NumberOfAppSession { compareString: compareString! number: String! } input NumberOfDaysInput { days: String } type NumberOfDays { days: String! } input PageInput { dynamicData: [] index: Int name: String } type Page { dynamicData: []! index: Int! name: String! } input PercentageOfSessionsInput { prec: String } type PercentageOfSessions { prec: String! } input TimeInForegroundInput { compareString: compareString time: String } type TimeInForeground { compareString: compareString! time: String! } input TriggerDataInput { declined: numberOfDaysInput genericRule: String mobileappVersion: [String] mobileosVersion: [String] numberOfAppSession: numberOfAppSessionInput percentageOfSessions: percentageOfSessionsInput submitted: numberOfDaysInput timeInForeground: timeInForegroundInput } type TriggerData { declined: numberOfDays genericRule: String mobileappVersion: [String] mobileosVersion: [String] numberOfAppSession: numberOfAppSession percentageOfSessions: percentageOfSessions submitted: numberOfDays timeInForeground: timeInForeground } enum CompareString { EQUALS GREATERTHAN SMALLERTHAN } enum FormType { CODE MOBILEINVITATION } enum RatingViewType { BARS EMOTICONS GRADINGDROPDOWN GRADINGSLIDER NUMBERS NUMBERSSQUARE SLIDE SMILIES STARS } schema { query: Query mutation: Mutation } DependsOn: - GqlApi AccessTokenWrapperDataSource: Type: AWS::AppSync::DataSource Properties: ApiId: !GetAtt GqlApi.ApiId LambdaConfig: LambdaFunctionArn: Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-AccessTokenWrapper' Name: access_token_wrapper_lambda ServiceRoleArn: !GetAtt DataSourceRole.Arn Type: AWS_LAMBDA DependsOn: - DataSourceRole - GqlApi PostAccessTokenWrapperResolver: Type: AWS::AppSync::Resolver Properties: ApiId: !GetAtt GqlApi.ApiId FieldName: postAccessTokenWrapper TypeName: Mutation DataSourceName: !GetAtt AccessTokenWrapperDataSource.Name RequestMappingTemplate: | #set( $cognitoIdentityId = $context.identity.cognitoIdentityId ) #if($context.identity.userArn.matches('arn:aws:iam::\d+:user/.*')) #if($context.args.cognitoIdentityId__) #set( $cognitoIdentityId = $context.args.cognitoIdentityId__ ) #elseif( $context.source.cognitoIdentityId__ ) #set( $cognitoIdentityId = $context.source.cognitoIdentityId__ ) #end #end #set( $unamepwd = "$util.urlEncode($cognitoIdentityId):" ) { "version" : "2017-02-28", "operation": "Invoke", "payload": { "source": "AppSync", "httpMethod": "post", "headers": { "Authorization": "Basic $util.base64Encode($unamepwd)", }, "pathParameters": { }, "queryStringParameters": { } } } ResponseMappingTemplate: | #if($context.result.statusCode >= 200 && $context.result.statusCode <= 299) #set( $result = $util.parseJson($context.result.body) ) #if($context.args.cognitoIdentityId__) #set( $result.cognitoIdentityId__ = $context.args.cognitoIdentityId__ ) #elseif($context.source.cognitoIdentityId__) #set( $result.cognitoIdentityId__ = $context.source.cognitoIdentityId__ ) #end $util.toJson($result) #else $utils.error($util.parseJson($context.result.body).message, $context.result.statusCode.toString(), $util.parseJson($context.result.body)) #end DependsOn: - GqlApi - GqlSchema - AccessTokenWrapperDataSource ConfigurationDataSource: Type: AWS::AppSync::DataSource Properties: ApiId: !GetAtt GqlApi.ApiId LambdaConfig: LambdaFunctionArn: Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-Configuration' Name: configuration_lambda ServiceRoleArn: !GetAtt DataSourceRole.Arn Type: AWS_LAMBDA DependsOn: - DataSourceRole - GqlApi GetConfigurationResolver: Type: AWS::AppSync::Resolver Properties: ApiId: !GetAtt GqlApi.ApiId FieldName: getConfiguration TypeName: Query DataSourceName: !GetAtt ConfigurationDataSource.Name RequestMappingTemplate: | #set( $cognitoIdentityId = $context.identity.cognitoIdentityId ) #if($context.identity.userArn.matches('arn:aws:iam::\d+:user/.*')) #if($context.args.cognitoIdentityId__) #set( $cognitoIdentityId = $context.args.cognitoIdentityId__ ) #elseif( $context.source.cognitoIdentityId__ ) #set( $cognitoIdentityId = $context.source.cognitoIdentityId__ ) #end #end #set( $unamepwd = "$util.urlEncode($cognitoIdentityId):" ) { "version" : "2017-02-28", "operation": "Invoke", "payload": { "source": "AppSync", "httpMethod": "get", "headers": { "Authorization": "Basic $util.base64Encode($unamepwd)", }, "pathParameters": { }, "queryStringParameters": { } } } ResponseMappingTemplate: | #if($context.result.statusCode >= 200 && $context.result.statusCode <= 299) #set( $result = $util.parseJson($context.result.body) ) #if($context.args.cognitoIdentityId__) #set( $result.cognitoIdentityId__ = $context.args.cognitoIdentityId__ ) #elseif($context.source.cognitoIdentityId__) #set( $result.cognitoIdentityId__ = $context.source.cognitoIdentityId__ ) #end $util.toJson($result) #else $utils.error($util.parseJson($context.result.body).message, $context.result.statusCode.toString(), $util.parseJson($context.result.body)) #end DependsOn: - GqlApi - GqlSchema - ConfigurationDataSource FeedbackDataSource: Type: AWS::AppSync::DataSource Properties: ApiId: !GetAtt GqlApi.ApiId LambdaConfig: LambdaFunctionArn: Fn::Join: - '' - - 'arn:aws:lambda:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':function:' - Fn::ImportValue: !Sub '${FunctionStack}-functions-Feedback' Name: feedback_lambda ServiceRoleArn: !GetAtt DataSourceRole.Arn Type: AWS_LAMBDA DependsOn: - DataSourceRole - GqlApi PostFeedbackResolver: Type: AWS::AppSync::Resolver Properties: ApiId: !GetAtt GqlApi.ApiId FieldName: postFeedback TypeName: Mutation DataSourceName: !GetAtt FeedbackDataSource.Name RequestMappingTemplate: | #set( $cognitoIdentityId = $context.identity.cognitoIdentityId ) #if($context.identity.userArn.matches('arn:aws:iam::\d+:user/.*')) #if($context.args.cognitoIdentityId__) #set( $cognitoIdentityId = $context.args.cognitoIdentityId__ ) #elseif( $context.source.cognitoIdentityId__ ) #set( $cognitoIdentityId = $context.source.cognitoIdentityId__ ) #end #end #set( $unamepwd = "$util.urlEncode($cognitoIdentityId):" ) { "version" : "2017-02-28", "operation": "Invoke", "payload": { "source": "AppSync", "httpMethod": "post", "body": "$util.escapeJavaScript($util.toJson($context.args.feedback))", "headers": { "Authorization": "Basic $util.base64Encode($unamepwd)", }, "pathParameters": { }, "queryStringParameters": { } } } ResponseMappingTemplate: | #if($context.result.statusCode >= 200 && $context.result.statusCode <= 299) #set( $result = $util.parseJson($context.result.body) ) #if($context.args.cognitoIdentityId__) #set( $result.cognitoIdentityId__ = $context.args.cognitoIdentityId__ ) #elseif($context.source.cognitoIdentityId__) #set( $result.cognitoIdentityId__ = $context.source.cognitoIdentityId__ ) #end $util.toJson($result) #else $utils.error($util.parseJson($context.result.body).message, $context.result.statusCode.toString(), $util.parseJson($context.result.body)) #end DependsOn: - GqlApi - GqlSchema - FeedbackDataSource Outputs: ApiEndpoint: Description: The endpoint for the AppSync API. Value: !GetAtt GqlApi.GraphQLUrl