-
Notifications
You must be signed in to change notification settings - Fork 14
feat: add notification and AWS utils to connector package #6534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
dbe76fb
Initial commit
thiessenp-cds 7787ecc
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 8e08762
Move notification to core
thiessenp-cds c0d2a78
Add missing dependencies
thiessenp-cds f91b340
Update comment
thiessenp-cds 60f8689
Remove sqs queue url from connectors
thiessenp-cds 854d837
Add more comments
thiessenp-cds 9134232
Undo previous change
thiessenp-cds d1ef95c
Merge branch 'main' into feat/notification-package-update
thiessenp-cds c51419a
Merge branch 'main' into feat/notification-package-update
thiessenp-cds e8bfe99
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 157ded2
Move notification and utils to connectors
thiessenp-cds c7e8586
Undo previous package change
thiessenp-cds d980a0d
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 24f149b
Updatee error logging
thiessenp-cds 9ab3837
Update logging typo
thiessenp-cds ab7a4ae
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 0ea5e83
Update notification utils to add cause to re-thrown errors
thiessenp-cds c00ddb3
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 249f725
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 08fe30e
Fix typo
thiessenp-cds b435a3f
Updates from PR review
thiessenp-cds f0376f0
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 92b2ec1
Merge branch 'main' into feat/notification-package-update
thiessenp-cds 594c420
Remove some no longer needed exports
thiessenp-cds aa368c0
Remove export from barrel file
thiessenp-cds File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export { GCNotifyConnector, type Personalisation } from "./gc-notify-connector"; | ||
| export { PostgresConnector } from "./postgres-connector"; | ||
| export { notification } from "./notification"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; | ||
| import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs"; | ||
| import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; | ||
| import { randomUUID } from "crypto"; | ||
| import { ErrorWithCause } from "./types/errors"; | ||
| import { getAwsSQSQueueURL } from "./utils"; | ||
|
|
||
| const DYNAMODB_NOTIFICATION_TABLE_NAME = "Notification"; | ||
|
|
||
| const globalConfig = { | ||
| region: process.env.AWS_REGION ?? "ca-central-1", | ||
| }; | ||
|
|
||
| const dynamoDBDocumentClient = DynamoDBDocumentClient.from( | ||
| new DynamoDBClient({ | ||
| ...globalConfig, | ||
| // SDK retries use exponential backoff with jitter by default | ||
| maxAttempts: 15, | ||
| }) | ||
| ); | ||
|
|
||
| const sqsClient = new SQSClient({ | ||
| ...globalConfig, | ||
| }); | ||
|
|
||
| /** | ||
| * Creates a notification record in DynamoDB and enqueues it for immediate sending. | ||
| * | ||
| * @param emails - Array of email addresses to send the notification to | ||
| * @param subject - Email subject line | ||
| * @param body - Email body content | ||
| */ | ||
| const sendImmediate = async ({ | ||
| emails, | ||
| subject, | ||
| body, | ||
| }: { | ||
| notificationId?: string; | ||
| emails: string[]; | ||
| subject: string; | ||
| body: string; | ||
| }): Promise<void> => { | ||
| const notificationId = randomUUID(); | ||
| try { | ||
| await _createRecord({ notificationId, emails, subject, body }); | ||
| await enqueueDeferred(notificationId); | ||
| } catch (error) { | ||
| throw new ErrorWithCause(`Error creating immediate notification id ${notificationId}`, { | ||
| cause: error, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Creates a notification record in DynamoDB for deferred sending. Once the related | ||
| * process is completed it can enqueue the notification for sending by calling | ||
| * enqueueDeferredNotification with the related notificationId. | ||
| * | ||
| * @param notificationId - Unique identifier for the notification to enqueue and | ||
| * used by the notification lambda to look up the record in DynamoDB. | ||
| */ | ||
| const sendDeferred = async ({ | ||
| notificationId, | ||
| emails, | ||
| subject, | ||
| body, | ||
| }: { | ||
| notificationId: string; | ||
| emails: string[]; | ||
| subject: string; | ||
| body: string; | ||
| }): Promise<void> => { | ||
| try { | ||
| await _createRecord({ notificationId, emails, subject, body }); | ||
| } catch (error) { | ||
| throw new ErrorWithCause(`Error creating deferred notification id ${notificationId}`, { | ||
| cause: error, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| const _createRecord = async ({ | ||
| notificationId, | ||
| emails, | ||
| subject, | ||
| body, | ||
| }: { | ||
| notificationId: string; | ||
| emails: string[]; | ||
| subject: string; | ||
| body: string; | ||
| }): Promise<void> => { | ||
| try { | ||
| const ttl = Math.floor(Date.now() / 1000) + 86400; // 24 hours from now | ||
| const command = new PutCommand({ | ||
| TableName: DYNAMODB_NOTIFICATION_TABLE_NAME, | ||
| Item: { | ||
| NotificationID: notificationId, | ||
| Emails: emails, | ||
| Subject: subject, | ||
| Body: body, | ||
| TTL: ttl, | ||
| }, | ||
| }); | ||
| await dynamoDBDocumentClient.send(command); | ||
| } catch (error) { | ||
| throw new ErrorWithCause(`Could not create record`, { cause: error }); | ||
| } | ||
| }; | ||
|
|
||
| const enqueueDeferred = async (notificationId: string): Promise<void> => { | ||
| try { | ||
| const queueUrl = await getAwsSQSQueueURL("NOTIFICATION_QUEUE_URL", "notification_queue"); | ||
| if (!queueUrl) { | ||
| throw new Error("Notification Queue not connected"); | ||
| } | ||
|
|
||
| const command = new SendMessageCommand({ | ||
| MessageBody: JSON.stringify({ notificationId }), | ||
| QueueUrl: queueUrl, | ||
| }); | ||
| const sendMessageCommandOutput = await sqsClient.send(command); | ||
| if (!sendMessageCommandOutput.MessageId) { | ||
| throw new Error("Received null SQS message identifier"); | ||
| } | ||
| } catch (error) { | ||
| throw new ErrorWithCause(`Could not enqueue`, { cause: error }); | ||
| } | ||
| }; | ||
|
|
||
| export const notification = { | ||
| sendImmediate, | ||
| sendDeferred, | ||
| enqueueDeferred, | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /** | ||
| * Custom Error class that supports adding a "cause" for re-throwing errors. | ||
| */ | ||
| export class ErrorWithCause extends Error { | ||
| cause?: unknown; | ||
|
|
||
| constructor(message: string, options?: { cause?: unknown }) { | ||
| super(message); | ||
| this.name = "ErrorWithCause"; | ||
| this.cause = options?.cause; | ||
|
|
||
| // Maintains proper stack trace | ||
| if (Error.captureStackTrace) { | ||
| Error.captureStackTrace(this, ErrorWithCause); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { SQSClient, GetQueueUrlCommand } from "@aws-sdk/client-sqs"; | ||
| import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager"; | ||
|
|
||
| const globalConfig = { | ||
| region: process.env.AWS_REGION ?? "ca-central-1", | ||
| }; | ||
|
|
||
| const sqsClient = new SQSClient({ | ||
| ...globalConfig, | ||
| }); | ||
|
|
||
| export function getAwsSecret(secretIdentifier: string): Promise<string | undefined> { | ||
| return new SecretsManagerClient() | ||
| .send(new GetSecretValueCommand({ SecretId: secretIdentifier })) | ||
| .then((commandOutput) => commandOutput.SecretString); | ||
| } | ||
|
|
||
| export const getAwsSQSQueueURL = async ( | ||
| urlEnvName: string, | ||
| urlQueueName: string | ||
| ): Promise<string | null> => { | ||
| if (process.env[urlEnvName]) { | ||
| return process.env[urlEnvName]; | ||
| } | ||
|
|
||
| const data = await sqsClient.send( | ||
| new GetQueueUrlCommand({ | ||
| QueueName: urlQueueName, | ||
| }) | ||
| ); | ||
| return data.QueueUrl ?? null; | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.