diff --git a/node/src/handlers/post-chat-feedback.js b/node/src/handlers/post-chat-feedback.js index ea59501a..4fe89636 100644 --- a/node/src/handlers/post-chat-feedback.js +++ b/node/src/handlers/post-chat-feedback.js @@ -1,5 +1,7 @@ const { wrap } = require("./middleware"); const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3"); +const { SNSClient, PublishCommand } = require("@aws-sdk/client-sns"); + const Validator = require("jsonschema").Validator; const feedbackSchema = { @@ -56,6 +58,11 @@ const handler = wrap(async (event) => { } await uploadToS3(`${content.sentiment}/${content.context.ref}`, content); + await sendNotification( + `Chat feedback: ${content.sentiment} response`, + JSON.stringify(content, null, 2) + ); + return { statusCode: 200, headers: { "content-type": "text/plain" }, @@ -76,7 +83,7 @@ const handler = wrap(async (event) => { const uploadToS3 = async (key, body) => { const client = new S3Client({}); const command = new PutObjectCommand({ - Bucket: process.env.MEDIA_CONVERT_DESTINATION_BUCKET, + Bucket: process.env.CHAT_FEEDBACK_BUCKET, Key: key, Body: JSON.stringify(body, null, 2), ContentType: "application/json", @@ -85,4 +92,16 @@ const uploadToS3 = async (key, body) => { return await client.send(command); }; -module.exports = { handler }; +const sendNotification = async (subject, message) => { + const snsClient = new SNSClient({}); + const command = new PublishCommand({ + TopicArn: process.env.CHAT_FEEDBACK_TOPIC_ARN, + Subject: subject, + Message: message, + }); + const response = await snsClient.send(command); + console.log(response); + return response; +}; + +module.exports = { handler, uploadToS3 }; diff --git a/node/src/package-lock.json b/node/src/package-lock.json index d5511670..a975c2da 100644 --- a/node/src/package-lock.json +++ b/node/src/package-lock.json @@ -18,6 +18,7 @@ "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", "@smithy/signature-v4": "^2.3.0", + "aws-sdk-client-mock": "^4.0.1", "axios": ">=0.21.1", "cookie": "^0.5.0", "debug": "^4.3.4", @@ -1902,6 +1903,50 @@ "typesafe-actions": "^5.1.0" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@smithy/abort-controller": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz", @@ -2992,12 +3037,44 @@ "@types/send": "*" } }, + "node_modules/@types/sinon": { + "version": "10.0.20", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", + "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/aws-sdk-client-mock": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.0.1.tgz", + "integrity": "sha512-yD2mmgy73Xce097G5hIpr1k7j50qzvJ49/+6osGZiCyk4m6cwhb+2x7kKFY1gEMwTzaS8+m8fXv9SB29SkRYyQ==", + "dev": true, + "dependencies": { + "@types/sinon": "^10.0.10", + "sinon": "^16.1.3", + "tslib": "^2.1.0" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/axios": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", @@ -3095,6 +3172,15 @@ "node": ">=0.10.0" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -3195,6 +3281,15 @@ "node": ">=12.20.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/http-status-codes": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", @@ -3243,6 +3338,12 @@ "npm": ">=1.4.28" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -3270,6 +3371,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -3363,6 +3470,28 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -3406,6 +3535,12 @@ "integrity": "sha512-xOoH7vzokDoenX4e3c+4ik8lf30kq9Pawq20TH5uq3RURsIJquqFTE0gS7OAEE6nvMQzuP5OXxubYuN7YLsTiw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3456,6 +3591,24 @@ "semver": "bin/semver" } }, + "node_modules/sinon": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", + "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "node_modules/sort-json": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/sort-json/-/sort-json-2.0.1.tgz", @@ -3488,6 +3641,18 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "dev": true }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -3500,6 +3665,15 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", diff --git a/node/src/package.json b/node/src/package.json index f698c793..68672b51 100644 --- a/node/src/package.json +++ b/node/src/package.json @@ -15,6 +15,7 @@ "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", "@smithy/signature-v4": "^2.3.0", + "aws-sdk-client-mock": "^4.0.1", "axios": ">=0.21.1", "cookie": "^0.5.0", "debug": "^4.3.4", diff --git a/node/test/integration/post-chat-feedback.test.js b/node/test/integration/post-chat-feedback.test.js index 06873c2c..0c5773b5 100644 --- a/node/test/integration/post-chat-feedback.test.js +++ b/node/test/integration/post-chat-feedback.test.js @@ -2,8 +2,8 @@ const chai = require("chai"); const expect = chai.expect; chai.use(require("chai-http")); const ApiToken = requireSource("api/api-token"); -const { mockClient } = require("aws-sdk-client-mock"); const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); +const { mockClient } = require("aws-sdk-client-mock"); const { handler } = requireSource("handlers/post-chat-feedback"); diff --git a/node/test/test-helpers/index.js b/node/test/test-helpers/index.js index 097dff30..18f77405 100644 --- a/node/test/test-helpers/index.js +++ b/node/test/test-helpers/index.js @@ -14,7 +14,7 @@ const TestEnvironment = { NUSSO_BASE_URL: "https://nusso-base.com/", NUSSO_API_KEY: "abc123", WEBSOCKET_URI: "wss://thisisafakewebsocketapiurl", - MEDIA_CONVERT_DESTINATION_BUCKET: "test-mediaconvert-destination-bucket", + CHAT_FEEDBACK_BUCKET: "test-mediaconvert-destination-bucket", }; for (const v in TestEnvironment) delete process.env[v]; diff --git a/template.yaml b/template.yaml index 0951bc55..e47a11e5 100644 --- a/template.yaml +++ b/template.yaml @@ -705,7 +705,8 @@ Resources: #* - !Ref apiDependencies Environment: Variables: - MEDIA_CONVERT_DESTINATION_BUCKET: !Ref MediaConvertDestinationBucket + CHAT_FEEDBACK_BUCKET: !Ref chatFeedbackBucket + CHAT_FEEDBACK_TOPIC_ARN: !Ref chatFeedbackTopic Policies: Version: 2012-10-17 Statement: @@ -713,7 +714,12 @@ Resources: Effect: Allow Action: - s3:PutObject - Resource: !Sub "arn:aws:s3:::${MediaConvertDestinationBucket}/*" + Resource: !Sub "arn:aws:s3:::${chatFeedbackBucket}/*" + - Sid: TopicAccess + Effect: Allow + Action: + - sns:Publish + Resource: !Ref chatFeedbackTopic Events: PostApi: Type: HttpApi @@ -721,6 +727,15 @@ Resources: ApiId: !Ref dcApi Path: /chat-feedback Method: POST + chatFeedbackBucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: !Sub "${AWS::StackName}-chat-feedback" + chatFeedbackTopic: + Type: AWS::SNS::Topic + Properties: + DisplayName: DC Chat Feedback + TopicName: !Sub "${AWS::StackName}-chat-feedback" defaultFunction: Type: AWS::Serverless::Function Properties: