diff --git a/src/domain/Challenge.ts b/src/domain/Challenge.ts index 155a410..2fbd82c 100644 --- a/src/domain/Challenge.ts +++ b/src/domain/Challenge.ts @@ -43,6 +43,7 @@ import { V5_TRACK_IDS_TO_NAMES, V5_TYPE_IDS_TO_NAMES } from "../common/Conversio import PaymentCreator, { PaymentDetail } from "../util/PaymentCreator"; import { getChallengeResources } from "../api/v5Api"; import m2mToken from "../helpers/MachineToMachineToken"; +import { sendHarmonyEvent } from "../helpers/Harmony"; if (!process.env.GRPC_ACL_SERVER_HOST || !process.env.GRPC_ACL_SERVER_PORT) { throw new Error("Missing required configurations GRPC_ACL_SERVER_HOST and GRPC_ACL_SERVER_PORT"); @@ -267,6 +268,7 @@ class ChallengeDomain extends CoreOperations { }; newChallenge = await super.create(challenge, metadata); + await sendHarmonyEvent("CREATE", "Challenge", newChallenge, input.billing?.billingAccountId!); } catch (err) { // Rollback lock amount if (baValidation != null) { @@ -536,6 +538,19 @@ class ChallengeDomain extends CoreOperations { }; updatedChallenge = await super.update(scanCriteria, dataToUpdate, metadata); + + const newChallenge = updatedChallenge.items[0]; + if (newChallenge.billing?.billingAccountId !== challenge?.billing?.billingAccountId) { + // For a New/Draft challenge, it might miss billing account id + // However when challenge activates, the billing account id will be provided (challenge-api validates it) + // In such case, send a CREATE event with whole challenge data (it's fine for search-indexer since it upserts for CREATE) + // Otherwise, the outer customer specified by the billing account id (like Topgear) will never receive a challenge CREATE event + await sendHarmonyEvent("CREATE", "Challenge", newChallenge, newChallenge.billing?.billingAccountId); + } else { + // Send only the updated data + // Some field like chanllege description could be big, don't include them if they're not actually updated + await sendHarmonyEvent("UPDATE", "Challenge", { ...dataToUpdate, id: newChallenge.id }, newChallenge.billing?.billingAccountId); + } } catch (err) { if (baValidation != null) { await lockConsumeAmount(baValidation, true); @@ -725,12 +740,14 @@ class ChallengeDomain extends CoreOperations { if (baValidation != null) await lockConsumeAmount(baValidation); try { await super.update(scanCriteria, dynamoUpdate); + await sendHarmonyEvent("UPDATE", "Challenge", { ...data, id }, challenge.billing?.billingAccountId); } catch (err) { if (baValidation != null) await lockConsumeAmount(baValidation, true); throw err; } } else { await super.update(scanCriteria, dynamoUpdate); + await sendHarmonyEvent("UPDATE", "Challenge", { ...data, id }, challenge.billing?.billingAccountId); console.log("Challenge Completed"); const completedChallenge = await this.lookup(DomainHelper.getLookupCriteria("id", id)); @@ -805,6 +822,7 @@ class ChallengeDomain extends CoreOperations { try { const result = await super.delete(lookupCriteria); + await sendHarmonyEvent("DELETE", "Challenge", { id: challenge.id }, challenge.billing?.billingAccountId); return result; } catch (err) { await lockConsumeAmount(baValidation, true); diff --git a/src/helpers/Harmony.ts b/src/helpers/Harmony.ts new file mode 100644 index 0000000..ebf9a2c --- /dev/null +++ b/src/helpers/Harmony.ts @@ -0,0 +1,46 @@ +import _ from "lodash"; +import { InvokeCommand, LambdaClient } from "@aws-sdk/client-lambda"; + +import { EVENT_ORIGINATOR } from "../common/Constants"; + +const FunctionName = + process.env.HARMONY_LAMBDA_FUNCTION ?? + "arn:aws:lambda:us-east-1:811668436784:function:harmony-api-dev-processMessage"; + +const harmonyClient = new LambdaClient({ region: process.env.AWS_REGION, maxAttempts: 2 }); + +/** + * Send event to Harmony. + * @param eventType The event type + * @param payloadType The payload type + * @param payload The event payload + * @param billingAccountId The billing account id + */ +export async function sendHarmonyEvent(eventType: string, payloadType: string, payload: object, billingAccountId?: number) { + const event = { + publisher: EVENT_ORIGINATOR, + timestamp: new Date().getTime(), + eventType, + payloadType, + payload, + billingAccountId, + }; + + const invokeCommand = new InvokeCommand({ + FunctionName, + InvocationType: "RequestResponse", + Payload: JSON.stringify(event), + LogType: "None" + }); + + const result = await harmonyClient.send(invokeCommand); + + if (result.FunctionError) { + console.error( + "Failed to send Harmony event", + result.FunctionError, + result.Payload?.transformToString() + ); + throw new Error(result.FunctionError); + } +}