Skip to content

Commit

Permalink
Publish Feast/Apple sub notifications to SQS
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwadeson committed Mar 13, 2024
1 parent a317e31 commit 5f4c199
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 2 deletions.
52 changes: 52 additions & 0 deletions cloudformation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ Resources:
Stage: !Ref Stage
Stack: !Ref Stack
App: !Ref App
QueueUrl: !Ref FeastAppleSubscriptionsQueue
Description: Records play store events
MemorySize: 128
Timeout: 29
Expand Down Expand Up @@ -688,6 +689,35 @@ Resources:
- Key: App
Value: !Ref App

FeastAppleSubscriptionsQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Sub ${App}-${Stage}-feast-apple-subscriptions-to-fetch
RedrivePolicy:
deadLetterTargetArn: !GetAtt FeastAppleSubscriptionsQueueDlq.Arn
maxReceiveCount: 8
KmsMasterKeyId: alias/aws/sqs
Tags:
- Key: Stage
Value: !Ref Stage
- Key: Stack
Value: !Ref Stack
- Key: App
Value: !Ref App

FeastAppleSubscriptionsQueueDlq:
Type: AWS::SQS::Queue
Properties:
QueueName: !Sub ${App}-${Stage}-feast-apple-subscriptions-to-fetch-dlq
KmsMasterKeyId: alias/aws/sqs
Tags:
- Key: Stage
Value: !Ref Stage
- Key: Stack
Value: !Ref Stack
- Key: App
Value: !Ref App

GoogleTokenRefreshFailureAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
Expand Down Expand Up @@ -1018,6 +1048,28 @@ Resources:
- Ref: AlarmTopic
TreatMissingData: notBreaching

FeastAppleSubscriptionDlqDepthAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
ActionsEnabled:
!FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
AlarmDescription: "Ensure that the Feast Apple subscription dead letter queue is empty"
Namespace: "AWS/SQS"
MetricName: ApproximateNumberOfMessagesVisible
Dimensions:
- Name: QueueName
Value: !GetAtt "FeastAppleSubscriptionsQueueDlq.QueueName"
Period: 60
Statistic: Sum
EvaluationPeriods: 1
ComparisonOperator: GreaterThanThreshold
Threshold: 0
AlarmActions:
- Ref: AlarmTopic
OKActions:
- Ref: AlarmTopic
TreatMissingData: notBreaching

AppleRevalidateSubscriptionRole:
Type: AWS::IAM::Role
Properties:
Expand Down
39 changes: 37 additions & 2 deletions typescript/src/feast/pubsub/pubsub.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
import {APIGatewayProxyEvent, APIGatewayProxyResult} from "aws-lambda";
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import { parsePayload, toSqsSubReference } from "../../pubsub/apple";
import { AppleSubscriptionReference } from "../../models/subscriptionReference";
import Sqs from 'aws-sdk/clients/sqs';
import { sendToSqs } from "../../utils/aws";
import { AWSError } from "aws-sdk";
import { PromiseResult } from "aws-sdk/lib/request";
import { HTTPResponses } from "../../models/apiGatewayHttp";

export async function handler(request: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
console.log(`[34ef7aa3] ${JSON.stringify(request)}`)
return { statusCode: 200, body: JSON.stringify(request) }
return processEvent()(request)
}

export function processEvent(
sendMessageToSqs: (queueUrl: string, message: AppleSubscriptionReference) => Promise<PromiseResult<Sqs.SendMessageResult, AWSError>> = sendToSqs
): (request: APIGatewayProxyEvent) => Promise<APIGatewayProxyResult> {
return async (request: APIGatewayProxyEvent) => {

const statusUpdateNotification =
parsePayload(request.body)
if (statusUpdateNotification instanceof Error) {
return HTTPResponses.INVALID_REQUEST
}

const appleSubscriptionReference =
toSqsSubReference(statusUpdateNotification)

try {
const queueUrl = process.env.QueueUrl;
if (queueUrl === undefined) throw new Error("No QueueUrl env parameter provided");

await sendMessageToSqs(queueUrl, appleSubscriptionReference)

return HTTPResponses.OK
} catch (e) {
console.error("Internal server error", e);
return HTTPResponses.INTERNAL_ERROR
}
}
}
89 changes: 89 additions & 0 deletions typescript/tests/feast/pubsub/pubsub.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { StatusUpdateNotification } from "../../../src/pubsub/apple";
import Mock = jest.Mock;
import { APIGatewayProxyEvent } from "aws-lambda";
import { processEvent } from "../../../src/feast/pubsub/pubsub";
import { HTTPResponses } from "../../../src/models/apiGatewayHttp";

describe("The Feast Apple pubsub", () => {
test("Should return HTTP 200 and publish the event to SQS", () => {
process.env['QueueUrl'] = "";

const mockSqsFunction: Mock<Promise<any>, [string, {receipt: string}]> = jest.fn((queueurl, event) => Promise.resolve({}));

const body: StatusUpdateNotification = {
original_transaction_id: "TEST",
cancellation_date: "TEST",
web_order_line_item_id: "TEST",
auto_renew_adam_id: "TEST",
expiration_intent: "TEST",
auto_renew_product_id: "uk.co.guardian.gla.12months.2018Dec.withFreeTrial",
auto_renew_status: "true",
bid: "uk.co.guardian.iphone2",
bvrs: "TEST",
environment: "Sandbox",
notification_type: "INITIAL_BUY",
unified_receipt: {
environment: "Sandbox",
latest_receipt: "TEST",
latest_receipt_info: [{
app_item_id: "TEST",
bvrs: "TEST",
is_in_intro_offer_period: "false",
is_trial_period: "true",
item_id: "TEST",
original_transaction_id: "TEST",
product_id: "uk.co.guardian.gla.12months.2018Dec.withFreeTrial",
quantity: "1",
transaction_id: "TEST",
unique_identifier: "TEST",
unique_vendor_identifier: "TEST",
version_external_identifier: "TEST",
web_order_line_item_id: "TEST",
purchase_date_ms: "TEST",
original_purchase_date_ms: "TEST",
expires_date: "TEST",
expires_date_ms: "TEST"
}],
pending_renewal_info: [
{
auto_renew_product_id: "uk.co.guardian.gla.12months.2018Dec.withFreeTrial",
auto_renew_status: "1",
original_transaction_id: "TEST",
product_id: "uk.co.guardian.gla.12months.2018Dec.withFreeTrial",
price_consent_status: '',
price_increase_status: '',
}
],
status: 0
},
promotional_offer_id: "promotional_offer_id",
promotional_offer_name: "promotional_offer_name",
product_id: "product_id",
purchase_date_ms: 0,
expires_date_ms: 0
}

const input: APIGatewayProxyEvent = {
body: JSON.stringify(body),
headers: {},
multiValueHeaders: {},
httpMethod: "POST",
isBase64Encoded: false,
path: '',
pathParameters: {},
multiValueQueryStringParameters: {},
// @ts-ignore
requestContext: null,
resource: '',

}

const expectedSubscriptionReferenceInSqs = {receipt: "TEST"};

return processEvent(mockSqsFunction)(input).then(result => {
expect(result).toStrictEqual(HTTPResponses.OK);
expect(mockSqsFunction.mock.calls.length).toEqual(1);
expect(mockSqsFunction.mock.calls[0][1]).toStrictEqual(expectedSubscriptionReferenceInSqs);
});
});
});

0 comments on commit 5f4c199

Please sign in to comment.