mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 05:23:12 +00:00
Replace Dataddo/Aurora payroll pipeline with email-triggered notifications
SES receives Gusto payroll emails at payroll@int.seahaven.com, stores to S3, Lambda parses and posts a combined Slack notification (employee payroll + contractor payments in one message) after a 10-minute SQS batching window. Removes Aurora Serverless, notifyPayroll Lambda, and @aws-sdk/client-rds-data. Adds mailparser, SQS delay queue, and processPayrollEmail Lambda.
This commit is contained in:
parent
8f409d0e63
commit
fe7c9cebca
5 changed files with 1068 additions and 468 deletions
665
package-lock.json
generated
665
package-lock.json
generated
|
|
@ -9,11 +9,12 @@
|
|||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-dynamodb": "^3.600.0",
|
||||
"@aws-sdk/client-rds-data": "^3.1037.0",
|
||||
"@aws-sdk/client-s3": "^3.600.0",
|
||||
"@aws-sdk/client-sqs": "^3.600.0",
|
||||
"@aws-sdk/client-ssm": "^3.600.0",
|
||||
"@aws-sdk/lib-dynamodb": "^3.600.0",
|
||||
"csv-parse": "^5.5.0"
|
||||
"csv-parse": "^5.5.0",
|
||||
"mailparser": "^3.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/crc32": {
|
||||
|
|
@ -272,56 +273,6 @@
|
|||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-rds-data": {
|
||||
"version": "3.1037.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-rds-data/-/client-rds-data-3.1037.0.tgz",
|
||||
"integrity": "sha512-Q15Y/ZlIw/yQHtohMwb6s9KhiIIneQ1GNrewVAiXMy3mY9hK5D2w4AP7qAPPmOA8P3qypzHogfWs9+0ncsGJOw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.36",
|
||||
"@aws-sdk/middleware-host-header": "^3.972.10",
|
||||
"@aws-sdk/middleware-logger": "^3.972.10",
|
||||
"@aws-sdk/middleware-recursion-detection": "^3.972.11",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.35",
|
||||
"@aws-sdk/region-config-resolver": "^3.972.13",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@aws-sdk/util-endpoints": "^3.996.8",
|
||||
"@aws-sdk/util-user-agent-browser": "^3.972.10",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.21",
|
||||
"@smithy/config-resolver": "^4.4.17",
|
||||
"@smithy/core": "^3.23.17",
|
||||
"@smithy/fetch-http-handler": "^5.3.17",
|
||||
"@smithy/hash-node": "^4.2.14",
|
||||
"@smithy/invalid-dependency": "^4.2.14",
|
||||
"@smithy/middleware-content-length": "^4.2.14",
|
||||
"@smithy/middleware-endpoint": "^4.4.32",
|
||||
"@smithy/middleware-retry": "^4.5.5",
|
||||
"@smithy/middleware-serde": "^4.2.20",
|
||||
"@smithy/middleware-stack": "^4.2.14",
|
||||
"@smithy/node-config-provider": "^4.3.14",
|
||||
"@smithy/node-http-handler": "^4.6.1",
|
||||
"@smithy/protocol-http": "^5.3.14",
|
||||
"@smithy/smithy-client": "^4.12.13",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/url-parser": "^4.2.14",
|
||||
"@smithy/util-base64": "^4.3.2",
|
||||
"@smithy/util-body-length-browser": "^4.2.2",
|
||||
"@smithy/util-body-length-node": "^4.2.3",
|
||||
"@smithy/util-defaults-mode-browser": "^4.3.49",
|
||||
"@smithy/util-defaults-mode-node": "^4.2.54",
|
||||
"@smithy/util-endpoints": "^3.4.2",
|
||||
"@smithy/util-middleware": "^4.2.14",
|
||||
"@smithy/util-retry": "^4.3.4",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-s3": {
|
||||
"version": "3.1027.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1027.0.tgz",
|
||||
|
|
@ -388,6 +339,58 @@
|
|||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-sqs": {
|
||||
"version": "3.1039.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.1039.0.tgz",
|
||||
"integrity": "sha512-U8rooijGy5OnSdxEaJ/sIxuGPgHfspxE8K/az22eJJ4qnO2XcVMtkHljvbfQG0yL3wd93XaypNSPI52okdTyCA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.38",
|
||||
"@aws-sdk/middleware-host-header": "^3.972.10",
|
||||
"@aws-sdk/middleware-logger": "^3.972.10",
|
||||
"@aws-sdk/middleware-recursion-detection": "^3.972.11",
|
||||
"@aws-sdk/middleware-sdk-sqs": "^3.972.22",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.37",
|
||||
"@aws-sdk/region-config-resolver": "^3.972.13",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@aws-sdk/util-endpoints": "^3.996.8",
|
||||
"@aws-sdk/util-user-agent-browser": "^3.972.10",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.23",
|
||||
"@smithy/config-resolver": "^4.4.17",
|
||||
"@smithy/core": "^3.23.17",
|
||||
"@smithy/fetch-http-handler": "^5.3.17",
|
||||
"@smithy/hash-node": "^4.2.14",
|
||||
"@smithy/invalid-dependency": "^4.2.14",
|
||||
"@smithy/md5-js": "^4.2.14",
|
||||
"@smithy/middleware-content-length": "^4.2.14",
|
||||
"@smithy/middleware-endpoint": "^4.4.32",
|
||||
"@smithy/middleware-retry": "^4.5.7",
|
||||
"@smithy/middleware-serde": "^4.2.20",
|
||||
"@smithy/middleware-stack": "^4.2.14",
|
||||
"@smithy/node-config-provider": "^4.3.14",
|
||||
"@smithy/node-http-handler": "^4.6.1",
|
||||
"@smithy/protocol-http": "^5.3.14",
|
||||
"@smithy/smithy-client": "^4.12.13",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/url-parser": "^4.2.14",
|
||||
"@smithy/util-base64": "^4.3.2",
|
||||
"@smithy/util-body-length-browser": "^4.2.2",
|
||||
"@smithy/util-body-length-node": "^4.2.3",
|
||||
"@smithy/util-defaults-mode-browser": "^4.3.49",
|
||||
"@smithy/util-defaults-mode-node": "^4.2.54",
|
||||
"@smithy/util-endpoints": "^3.4.2",
|
||||
"@smithy/util-middleware": "^4.2.14",
|
||||
"@smithy/util-retry": "^4.3.6",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-ssm": {
|
||||
"version": "3.1027.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.1027.0.tgz",
|
||||
|
|
@ -440,13 +443,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/core": {
|
||||
"version": "3.974.5",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.5.tgz",
|
||||
"integrity": "sha512-lMPlYlYfQdNZhlkJgnkmESwrY+hNh3PljmZ+37oAqLNdJ6rnILAwFSyc6B3bJeDOtMORNnMQIej0aTRuOlDyhQ==",
|
||||
"version": "3.974.7",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.7.tgz",
|
||||
"integrity": "sha512-YhRC90ofz5oolTJZlA8voU/oUrCB2azi8Usx51k8hhB5LpWbYQMMXKUqSqkoL0Cru+RQJgWTHpAfEDDIwfUhJw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@aws-sdk/xml-builder": "^3.972.19",
|
||||
"@aws-sdk/xml-builder": "^3.972.22",
|
||||
"@smithy/core": "^3.23.17",
|
||||
"@smithy/node-config-provider": "^4.3.14",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
|
|
@ -456,7 +459,7 @@
|
|||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/util-base64": "^4.3.2",
|
||||
"@smithy/util-middleware": "^4.2.14",
|
||||
"@smithy/util-retry": "^4.3.4",
|
||||
"@smithy/util-retry": "^4.3.6",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
|
|
@ -478,12 +481,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-env": {
|
||||
"version": "3.972.31",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.31.tgz",
|
||||
"integrity": "sha512-X/yGB73LmDW/6MdDJGCDzZBUXnM3ys4vs9l+5ZTJmiEswDdP1OjeoAFlFjVGS9o4KB2wZWQ9KOfdVNSSK6Ep3w==",
|
||||
"version": "3.972.33",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.33.tgz",
|
||||
"integrity": "sha512-bJV7eViSJV6GSuuN+VIdNVPdwPsNSf75BiC2v5alPrjR/OCcqgKwSZInKbDFz9mNeizldsyf67jt6YSIiv53Cw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
"@smithy/types": "^4.14.1",
|
||||
|
|
@ -494,12 +497,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-http": {
|
||||
"version": "3.972.33",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.33.tgz",
|
||||
"integrity": "sha512-c0ZF+lwoWVvX5iCaGKL5T/4DnIw88CGqxA0BcBs3U86mIp5EZYPVg+KSPkMXOyokmADvNewiMUfSG2uFwjRp0g==",
|
||||
"version": "3.972.35",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.35.tgz",
|
||||
"integrity": "sha512-x/BQGEIdq0oI+4WxLjKmnQvT7CnF9r8ezdGt7wXwxb7ckHXQz0Zmgxt8v3Ne0JaT3R5YefmuybHX6E8EnsDXyA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/fetch-http-handler": "^5.3.17",
|
||||
"@smithy/node-http-handler": "^4.6.1",
|
||||
|
|
@ -515,19 +518,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.972.35",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.35.tgz",
|
||||
"integrity": "sha512-jsU4u/cRkKFLKQS0k918FQ27fzXLG5ENiLWQMYE6581zLeI2hWh04ptlrvZMB3wJT/5d+vSzJk74X1CMFr4y8Q==",
|
||||
"version": "3.972.37",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.37.tgz",
|
||||
"integrity": "sha512-eUTpmWfd/BKsq9medhCRcu+GRAhFP2Zrn7/2jKDHHOOjCkhrMoTp/t4cEthqFoG7gE0VGp5wUxrXTdvBCmSmJg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.31",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.33",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.31",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.35",
|
||||
"@aws-sdk/nested-clients": "^3.997.3",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.33",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.37",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.33",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.37",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.37",
|
||||
"@aws-sdk/nested-clients": "^3.997.5",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/credential-provider-imds": "^4.2.14",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
|
|
@ -540,13 +543,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-login": {
|
||||
"version": "3.972.35",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.35.tgz",
|
||||
"integrity": "sha512-5oa3j0cA50jPqgNhZ9XdJVopuzUf1klRb28/2MfLYWWiPi9DRVvbrBWT+DidbHTT36520VuXZJahQwR+YgSjrg==",
|
||||
"version": "3.972.37",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.37.tgz",
|
||||
"integrity": "sha512-Ty68y8ISSC+g5Q3D0K8uAaoINwvfaOslnNpsF/LgVUxyosYXHawcK2yV4HLXDVugiTTYLQfJfcw0ce5meAGkKw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/nested-clients": "^3.997.3",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.5",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
"@smithy/protocol-http": "^5.3.14",
|
||||
|
|
@ -559,17 +562,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.972.36",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.36.tgz",
|
||||
"integrity": "sha512-4nT2T8Z7vH8KE9EdjEsuIlHpZSlcaK2PrKbQBjuUGU46BCCzF3WvP0u0Uiosni3Ykmmn4rWLVawoOCLotUtCbg==",
|
||||
"version": "3.972.38",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.38.tgz",
|
||||
"integrity": "sha512-BQ9XYnBDVxR2HuV5huXYQYF/PZMTsY+EnwfGnCU2cA8Zw63XpkOtPY8WqiMIZMQCrKPQQEiFURS/o9CIolRLqg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "^3.972.31",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.33",
|
||||
"@aws-sdk/credential-provider-ini": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.31",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.33",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.35",
|
||||
"@aws-sdk/credential-provider-ini": "^3.972.37",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.33",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.37",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.37",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/credential-provider-imds": "^4.2.14",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
|
|
@ -582,12 +585,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-process": {
|
||||
"version": "3.972.31",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.31.tgz",
|
||||
"integrity": "sha512-eKeT4MXumpBJsrDLCYcSzIkFPVTFn/es7It2oogp2OhU/ic7P/+xzFpQx9ZhwtXS57Mc5S42BPWi7lHmvs/nYg==",
|
||||
"version": "3.972.33",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.33.tgz",
|
||||
"integrity": "sha512-yfjGksI9WQbdMObb0VeLXqzTLI+a0qXLJT9gCDiv0+X/xjPpI3mTz6a5FibrhpuEKIe0gSgvs3MaoFZy5cx4WA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.9",
|
||||
|
|
@ -599,14 +602,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.972.35",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.35.tgz",
|
||||
"integrity": "sha512-bCuBdfnj0KGDMdLp6utMTLiJcFN2ek9EgZinxQZZSc3FxjJ/HSqeqab2cjbnoNfy8RM6suDCsRkmVY1izp9I+A==",
|
||||
"version": "3.972.37",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.37.tgz",
|
||||
"integrity": "sha512-fpwE+20ntpp3i9Xb9vUuQfXLDKYHH+5I2V+ZG96SX1nBzrruhy10RXDgmN7t1etOz3c55stlA3TeQASUA451NQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/nested-clients": "^3.997.3",
|
||||
"@aws-sdk/token-providers": "3.1036.0",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.5",
|
||||
"@aws-sdk/token-providers": "3.1039.0",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.9",
|
||||
|
|
@ -618,13 +621,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.972.35",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.35.tgz",
|
||||
"integrity": "sha512-swW6Bwvl8lanyEMtZOWE/oR6yqcRQH4HTQZUVsnDVgoXvRjRywpYpLv2BWwjUFyjPrqsdX6FeTkf4tMSe/qFTQ==",
|
||||
"version": "3.972.37",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.37.tgz",
|
||||
"integrity": "sha512-aryawqyebf+3WhAFNHfF62rekFpYtVcVN7dQ89qnAWsa4n5hJst8qBG6gXC24WHtW7Nnhkf9ScYnjwo0Brn3bw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/nested-clients": "^3.997.3",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.5",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.9",
|
||||
|
|
@ -820,12 +823,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
||||
"version": "3.972.34",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.34.tgz",
|
||||
"integrity": "sha512-/UL96JKjsjdodcRRMKl99tLQvK6Oi9ptLC9iU1yiTF/ruaDX0mtBBtnLNZDxIZRJOCVOtB49ed1YaTadqygk8Q==",
|
||||
"version": "3.972.36",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.36.tgz",
|
||||
"integrity": "sha512-YhPix+0x/MdQrb1Ug1GDKeS5fqylIy+naz800asX8II4jqfTk2KY2KhmmYCwZcky8YWtRQQwWCGdoqeAnip8Uw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@aws-sdk/util-arn-parser": "^3.972.3",
|
||||
"@smithy/core": "^3.23.17",
|
||||
|
|
@ -844,6 +847,23 @@
|
|||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-sdk-sqs": {
|
||||
"version": "3.972.22",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.22.tgz",
|
||||
"integrity": "sha512-DtR3mEiOUJcnEX/QuXmvbJto6xvQzp2ftnHb29c0aQYdmmzbKf0gsu9ovx1i/yy4ZR6m0rttTucS0iiP32dlGA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/smithy-client": "^4.12.13",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/util-hex-encoding": "^4.2.2",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-ssec": {
|
||||
"version": "3.972.9",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.9.tgz",
|
||||
|
|
@ -859,18 +879,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.972.35",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.35.tgz",
|
||||
"integrity": "sha512-hOFWNOjVmOocpRlrU04nYxjMOeoe0Obu5AXEuhB8zblMCPl3cG1hdluQCZERRKFyhMQjwZnDbhSHjoMUjetFGw==",
|
||||
"version": "3.972.37",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.37.tgz",
|
||||
"integrity": "sha512-N1oNpdiLoVAWYD3WFBnUi3LlfoDA06ZHo4ozyjbsJNLvILzvt//0CnR8N+CZ0NWeYgVB/5V59ivixHCWCx2ALw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@aws-sdk/util-endpoints": "^3.996.8",
|
||||
"@smithy/core": "^3.23.17",
|
||||
"@smithy/protocol-http": "^5.3.14",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/util-retry": "^4.3.4",
|
||||
"@smithy/util-retry": "^4.3.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -878,24 +898,24 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/nested-clients": {
|
||||
"version": "3.997.3",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.3.tgz",
|
||||
"integrity": "sha512-SivE6GP228IVgfsrr2c/vqTg95X0Qj39Yw4uIrcddpkUzIltNMoNOR62leHOLhODfjv9K8X2mPTwS69A5kT0nQ==",
|
||||
"version": "3.997.5",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.5.tgz",
|
||||
"integrity": "sha512-jGFr6DxtcMTmzOkG/a0jCZYv4BBDmeNYVeO+/memSoDkYCJu4Y58xviYmzwJfYyIVSts+X/BVjJm1uGBnwHEMg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/middleware-host-header": "^3.972.10",
|
||||
"@aws-sdk/middleware-logger": "^3.972.10",
|
||||
"@aws-sdk/middleware-recursion-detection": "^3.972.11",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.35",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.37",
|
||||
"@aws-sdk/region-config-resolver": "^3.972.13",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.22",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.24",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@aws-sdk/util-endpoints": "^3.996.8",
|
||||
"@aws-sdk/util-user-agent-browser": "^3.972.10",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.21",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.23",
|
||||
"@smithy/config-resolver": "^4.4.17",
|
||||
"@smithy/core": "^3.23.17",
|
||||
"@smithy/fetch-http-handler": "^5.3.17",
|
||||
|
|
@ -903,7 +923,7 @@
|
|||
"@smithy/invalid-dependency": "^4.2.14",
|
||||
"@smithy/middleware-content-length": "^4.2.14",
|
||||
"@smithy/middleware-endpoint": "^4.4.32",
|
||||
"@smithy/middleware-retry": "^4.5.5",
|
||||
"@smithy/middleware-retry": "^4.5.7",
|
||||
"@smithy/middleware-serde": "^4.2.20",
|
||||
"@smithy/middleware-stack": "^4.2.14",
|
||||
"@smithy/node-config-provider": "^4.3.14",
|
||||
|
|
@ -919,7 +939,7 @@
|
|||
"@smithy/util-defaults-mode-node": "^4.2.54",
|
||||
"@smithy/util-endpoints": "^3.4.2",
|
||||
"@smithy/util-middleware": "^4.2.14",
|
||||
"@smithy/util-retry": "^4.3.4",
|
||||
"@smithy/util-retry": "^4.3.6",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
|
|
@ -944,12 +964,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
||||
"version": "3.996.22",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.22.tgz",
|
||||
"integrity": "sha512-/rXhMXteD+BqhFd0nYprAgcZ/KtU+963uftPqd3tiFcFfooHZINXUGtOmo2SQjRVauCTNqIEzkwuSETdZFqTTA==",
|
||||
"version": "3.996.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.24.tgz",
|
||||
"integrity": "sha512-amP7tLikppN940wbBFISYqiuzVmpzMS9U3mcgtmVLjX4fdWI/SNCvrXv6ZxfVzTT4cT0rPKOLhFah2xLwzREWw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/middleware-sdk-s3": "^3.972.34",
|
||||
"@aws-sdk/middleware-sdk-s3": "^3.972.36",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/protocol-http": "^5.3.14",
|
||||
"@smithy/signature-v4": "^5.3.14",
|
||||
|
|
@ -961,13 +981,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.1036.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1036.0.tgz",
|
||||
"integrity": "sha512-aNSJ6jjDYayxN9ZA1JpycVScX93Lx03kKZ1EXt3DGOTahcWVLJj3oLAlop0xKP+vP2Ga2t49p1tEaMkTbCCaZA==",
|
||||
"version": "3.1039.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1039.0.tgz",
|
||||
"integrity": "sha512-NMSFL2HwkAOoCeLCQiqoOq5pT3vVbSjww2QZTuYgYknVwhhv125PSDzZIcL5EYnlxuPWjEOdauZK+FspkZDVdw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.5",
|
||||
"@aws-sdk/nested-clients": "^3.997.3",
|
||||
"@aws-sdk/core": "^3.974.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.5",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/property-provider": "^4.2.14",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.9",
|
||||
|
|
@ -1059,12 +1079,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/util-user-agent-node": {
|
||||
"version": "3.973.21",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.21.tgz",
|
||||
"integrity": "sha512-Av4UHTcAWgdvbN0IP9pbtf4Qa1+6LtJqQdZWj5pLn5J67w0pnJJAZZ+7JPPcj2KN3378zD2JDM9DwJKEyvyMTQ==",
|
||||
"version": "3.973.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.23.tgz",
|
||||
"integrity": "sha512-gGwq8L2Euw0aNG6Ey4EktiAo3fSCVoDy1CaBIthd+oeaKHPXUrNaApMewQ6La5Hv0lcznOtECZaNvYyc5LXXfA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.35",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.37",
|
||||
"@aws-sdk/types": "^3.973.8",
|
||||
"@smithy/node-config-provider": "^4.3.14",
|
||||
"@smithy/types": "^4.14.1",
|
||||
|
|
@ -1084,13 +1104,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/xml-builder": {
|
||||
"version": "3.972.19",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.19.tgz",
|
||||
"integrity": "sha512-Cw8IOMdBUEIl8ZlhRC3Dc/E64D5B5/8JhV6vhPLiPfJwcRC84S6F8aBOIi/N4vR9ZyA4I5Cc0Ateb/9EHaJXeQ==",
|
||||
"version": "3.972.22",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz",
|
||||
"integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@nodable/entities": "2.1.0",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"fast-xml-parser": "5.7.1",
|
||||
"fast-xml-parser": "5.7.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1118,6 +1139,19 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@selderee/plugin-htmlparser2": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
|
||||
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.3",
|
||||
"selderee": "^0.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/chunked-blob-reader": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz",
|
||||
|
|
@ -1353,12 +1387,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@smithy/md5-js": {
|
||||
"version": "4.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.13.tgz",
|
||||
"integrity": "sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==",
|
||||
"version": "4.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.14.tgz",
|
||||
"integrity": "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.14.0",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
|
|
@ -1400,19 +1434,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@smithy/middleware-retry": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.5.tgz",
|
||||
"integrity": "sha512-wnYOpB5vATFKWrY2Z9Alb0KhjZI6AbzU6Fbz3Hq2GnURdRYWB4q+qWivQtSTwXcmWUA3MZ6krfwL6Cq5MAbxsA==",
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz",
|
||||
"integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.23.17",
|
||||
"@smithy/node-config-provider": "^4.3.14",
|
||||
"@smithy/protocol-http": "^5.3.14",
|
||||
"@smithy/service-error-classification": "^4.3.0",
|
||||
"@smithy/service-error-classification": "^4.3.1",
|
||||
"@smithy/smithy-client": "^4.12.13",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"@smithy/util-middleware": "^4.2.14",
|
||||
"@smithy/util-retry": "^4.3.4",
|
||||
"@smithy/util-retry": "^4.3.6",
|
||||
"@smithy/uuid": "^1.1.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
|
|
@ -1532,9 +1566,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@smithy/service-error-classification": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.0.tgz",
|
||||
"integrity": "sha512-9jKsBYQRPR0xBLgc2415RsA5PIcP2sis4oBdN9s0D13cg1B1284mNTjx9Yc+BEERXzuPm5ObktI96OxsKh8E9A==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz",
|
||||
"integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.14.1"
|
||||
|
|
@ -1755,12 +1789,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@smithy/util-retry": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.4.tgz",
|
||||
"integrity": "sha512-FY1UQQ1VFmMwiYp1GVS4MeaGD5O0blLNYK0xCRHU+mJgeoH/hSY8Ld8sJWKQ6uznkh14HveRGQJncgPyNl9J+A==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.6.tgz",
|
||||
"integrity": "sha512-p6/FO1n2KxMeQyna067i0uJ6TSbb165ZhnRtCpWh4Foxqbfc6oW+XITaL8QkFJj3KFnDe2URt4gOhgU06EP9ew==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/service-error-classification": "^4.3.0",
|
||||
"@smithy/service-error-classification": "^4.3.1",
|
||||
"@smithy/types": "^4.14.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
|
|
@ -1837,6 +1871,41 @@
|
|||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@zone-eu/mailsplit": {
|
||||
"version": "5.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz",
|
||||
"integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==",
|
||||
"license": "(MIT OR EUPL-1.1+)",
|
||||
"dependencies": {
|
||||
"libbase64": "1.3.0",
|
||||
"libmime": "5.3.7",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@zone-eu/mailsplit/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@zone-eu/mailsplit/node_modules/libmime": {
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
|
||||
"integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encoding-japanese": "2.2.0",
|
||||
"iconv-lite": "0.6.3",
|
||||
"libbase64": "1.3.0",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bowser": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
|
||||
|
|
@ -1849,6 +1918,91 @@
|
|||
"integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding-japanese": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz",
|
||||
"integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz",
|
||||
|
|
@ -1865,9 +2019,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.1.tgz",
|
||||
"integrity": "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz",
|
||||
"integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -1885,6 +2039,126 @@
|
|||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/html-to-text": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
|
||||
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@selderee/plugin-htmlparser2": "^0.11.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"htmlparser2": "^8.0.2",
|
||||
"selderee": "^0.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/leac": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
|
||||
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/libbase64": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz",
|
||||
"integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/libmime": {
|
||||
"version": "5.3.8",
|
||||
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.8.tgz",
|
||||
"integrity": "sha512-ZrCY+Q66mPvasAfjsQ/IgahzoBvfE1VdtGRpo1hwRB1oK3wJKxhKA3GOcd2a6j7AH5eMFccxK9fBoCpRZTf8ng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encoding-japanese": "2.2.0",
|
||||
"iconv-lite": "0.7.2",
|
||||
"libbase64": "1.3.0",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/libqp": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz",
|
||||
"integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mailparser": {
|
||||
"version": "3.9.8",
|
||||
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.8.tgz",
|
||||
"integrity": "sha512-7jSlFGXiianVnhnb6wdutJFloD34488nrHY7r6FNqwXAhZ7YiJDYrKKTxZJ0oSrXcAPHm8YoYnh97xyGtrBQ3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zone-eu/mailsplit": "5.4.8",
|
||||
"encoding-japanese": "2.2.0",
|
||||
"he": "1.2.0",
|
||||
"html-to-text": "9.0.5",
|
||||
"iconv-lite": "0.7.2",
|
||||
"libmime": "5.3.8",
|
||||
"linkify-it": "5.0.0",
|
||||
"nodemailer": "8.0.5",
|
||||
"punycode.js": "2.3.1",
|
||||
"tlds": "1.261.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mnemonist": {
|
||||
"version": "0.38.3",
|
||||
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz",
|
||||
|
|
@ -1894,12 +2168,34 @@
|
|||
"obliterator": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "8.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz",
|
||||
"integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/obliterator": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz",
|
||||
"integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parseley": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
|
||||
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"leac": "^0.6.0",
|
||||
"peberminta": "^0.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
|
||||
|
|
@ -1915,6 +2211,42 @@
|
|||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/peberminta": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
||||
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/selderee": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
|
||||
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parseley": "^0.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz",
|
||||
|
|
@ -1927,11 +2259,26 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tlds": {
|
||||
"version": "1.261.0",
|
||||
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz",
|
||||
"integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tlds": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-dynamodb": "^3.600.0",
|
||||
"@aws-sdk/client-rds-data": "^3.1037.0",
|
||||
"@aws-sdk/client-s3": "^3.600.0",
|
||||
"@aws-sdk/client-sqs": "^3.600.0",
|
||||
"@aws-sdk/client-ssm": "^3.600.0",
|
||||
"@aws-sdk/lib-dynamodb": "^3.600.0",
|
||||
"csv-parse": "^5.5.0"
|
||||
"csv-parse": "^5.5.0",
|
||||
"mailparser": "^3.7.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,187 +0,0 @@
|
|||
import { RDSDataClient, ExecuteStatementCommand } from "@aws-sdk/client-rds-data";
|
||||
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
||||
import { DynamoDBDocumentClient, GetCommand, PutCommand } from "@aws-sdk/lib-dynamodb";
|
||||
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
|
||||
|
||||
const rds = new RDSDataClient();
|
||||
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient());
|
||||
const ssm = new SSMClient();
|
||||
|
||||
const CLUSTER_ARN = process.env.AURORA_CLUSTER_ARN;
|
||||
const SECRET_ARN = process.env.AURORA_SECRET_ARN;
|
||||
const DATABASE = "payroll";
|
||||
const TABLE_NAME = process.env.TABLE_NAME;
|
||||
const CHANNEL_ID = "C0AV5RBMYKU";
|
||||
|
||||
let cachedToken;
|
||||
async function getSlackToken() {
|
||||
if (cachedToken) return cachedToken;
|
||||
const { Parameter } = await ssm.send(
|
||||
new GetParameterCommand({ Name: process.env.SLACK_BOT_TOKEN_PARAM, WithDecryption: true })
|
||||
);
|
||||
cachedToken = Parameter.Value;
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
async function sql(query) {
|
||||
const { records } = await rds.send(
|
||||
new ExecuteStatementCommand({
|
||||
resourceArn: CLUSTER_ARN,
|
||||
secretArn: SECRET_ARN,
|
||||
database: DATABASE,
|
||||
sql: query,
|
||||
})
|
||||
);
|
||||
return records || [];
|
||||
}
|
||||
|
||||
function extractValue(field) {
|
||||
if (field.stringValue !== undefined) return field.stringValue;
|
||||
if (field.longValue !== undefined) return field.longValue;
|
||||
if (field.doubleValue !== undefined) return field.doubleValue;
|
||||
if (field.booleanValue !== undefined) return field.booleanValue;
|
||||
if (field.isNull) return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const formatCurrency = (v) =>
|
||||
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(Number(v || 0));
|
||||
|
||||
const formatDate = (ts) => {
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", year: "numeric" });
|
||||
};
|
||||
|
||||
async function getNotifiedPayrolls() {
|
||||
const result = await ddb.send(
|
||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk: "PAYROLL_NOTIFIED" } })
|
||||
);
|
||||
return result.Item?.checkDates || [];
|
||||
}
|
||||
|
||||
async function markNotified(checkDate) {
|
||||
const existing = await getNotifiedPayrolls();
|
||||
await ddb.send(
|
||||
new PutCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Item: { pk: "PAYROLL_NOTIFIED", checkDates: [...existing, checkDate] },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function buildPayrollMessage(row) {
|
||||
const checkDate = extractValue(row[0]);
|
||||
const companyDebit = extractValue(row[1]);
|
||||
const grossPay = extractValue(row[2]);
|
||||
const netPay = extractValue(row[3]);
|
||||
const employerTaxes = extractValue(row[4]);
|
||||
const reimbursements = extractValue(row[5]);
|
||||
const benefits = extractValue(row[6]);
|
||||
|
||||
const contractorRows = await sql(
|
||||
`SELECT COALESCE(SUM(payments_wage_total), 0), COALESCE(SUM(payments_reimbursement), 0) FROM contractor_payments WHERE payments_date = '${checkDate}'`
|
||||
);
|
||||
const contractorWages = contractorRows.length > 0 ? extractValue(contractorRows[0][0]) : 0;
|
||||
const contractorReimbursements = contractorRows.length > 0 ? extractValue(contractorRows[0][1]) : 0;
|
||||
const contractorTotal = contractorWages + contractorReimbursements;
|
||||
|
||||
const totalGross = grossPay + contractorTotal;
|
||||
const cashRequirement = companyDebit + contractorTotal + benefits;
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
type: "header",
|
||||
text: { type: "plain_text", text: `Payroll Processed — ${formatDate(checkDate)}` },
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
fields: [
|
||||
{ type: "mrkdwn", text: `*Check Date*\n${formatDate(checkDate)}` },
|
||||
{ type: "mrkdwn", text: `*Total Gross Pay*\n${formatCurrency(totalGross)}` },
|
||||
],
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
type: "section",
|
||||
fields: [
|
||||
{ type: "mrkdwn", text: `*Employee Gross*\n${formatCurrency(grossPay)}` },
|
||||
{ type: "mrkdwn", text: `*Contractor Gross*\n${formatCurrency(contractorTotal)}` },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
fields: [
|
||||
{ type: "mrkdwn", text: `*Employee Net Pay*\n${formatCurrency(netPay)}` },
|
||||
{ type: "mrkdwn", text: `*Reimbursements*\n${formatCurrency(reimbursements)}` },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
fields: [
|
||||
{ type: "mrkdwn", text: `*Employer Taxes*\n${formatCurrency(employerTaxes)}` },
|
||||
{ type: "mrkdwn", text: `*Benefits (401k)*\n${formatCurrency(benefits)}` },
|
||||
],
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `:moneybag: *Total Bank Withdrawal on ${formatDate(checkDate)}: ${formatCurrency(cashRequirement)}*`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return { checkDate, blocks };
|
||||
}
|
||||
|
||||
export const handler = async () => {
|
||||
const payrollRows = await sql(
|
||||
"SELECT check_date, company_debit, gross_pay, net_pay, employer_taxes, reimbursements, benefits FROM payroll WHERE company_debit > 0 ORDER BY check_date DESC"
|
||||
);
|
||||
|
||||
if (payrollRows.length === 0) {
|
||||
console.log("No processed payrolls found");
|
||||
return { statusCode: 200, body: "No payrolls" };
|
||||
}
|
||||
|
||||
const notified = await getNotifiedPayrolls();
|
||||
const newPayrolls = payrollRows.filter(
|
||||
(row) => !notified.includes(extractValue(row[0]))
|
||||
);
|
||||
|
||||
if (newPayrolls.length === 0) {
|
||||
console.log("All payrolls already notified");
|
||||
return { statusCode: 200, body: "Already notified" };
|
||||
}
|
||||
|
||||
const token = await getSlackToken();
|
||||
|
||||
for (const row of newPayrolls) {
|
||||
const { checkDate, blocks } = await buildPayrollMessage(row);
|
||||
|
||||
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
channel: CHANNEL_ID,
|
||||
blocks,
|
||||
text: `Payroll processed for ${formatDate(checkDate)}`,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
console.error("Slack post failed:", data.error);
|
||||
throw new Error(`Slack API error: ${data.error}`);
|
||||
}
|
||||
|
||||
await markNotified(checkDate);
|
||||
console.log(`Notified for check date: ${checkDate}`);
|
||||
}
|
||||
|
||||
return { statusCode: 200, body: `Notified ${newPayrolls.length} payroll(s)` };
|
||||
};
|
||||
464
src/processPayrollEmail.js
Normal file
464
src/processPayrollEmail.js
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
||||
import {
|
||||
DynamoDBDocumentClient,
|
||||
GetCommand,
|
||||
PutCommand,
|
||||
ScanCommand,
|
||||
BatchWriteCommand,
|
||||
} from "@aws-sdk/lib-dynamodb";
|
||||
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
|
||||
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
|
||||
import { simpleParser } from "mailparser";
|
||||
|
||||
const s3 = new S3Client();
|
||||
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient());
|
||||
const sqs = new SQSClient();
|
||||
const ssm = new SSMClient();
|
||||
|
||||
const TABLE_NAME = process.env.TABLE_NAME;
|
||||
const CHANNEL_ID = process.env.PAYROLL_CHANNEL_ID;
|
||||
const QUEUE_URL = process.env.CONTRACTOR_BATCH_QUEUE_URL;
|
||||
|
||||
let cachedToken;
|
||||
async function getSlackToken() {
|
||||
if (cachedToken) return cachedToken;
|
||||
const { Parameter } = await ssm.send(
|
||||
new GetParameterCommand({
|
||||
Name: process.env.SLACK_BOT_TOKEN_PARAM,
|
||||
WithDecryption: true,
|
||||
})
|
||||
);
|
||||
cachedToken = Parameter.Value;
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
const formatCurrency = (v) =>
|
||||
new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(Number(v || 0));
|
||||
|
||||
function classifyEmail(from, subject, text) {
|
||||
const fromAddr = (from?.text || from || "").toLowerCase();
|
||||
const subj = (subject || "").toLowerCase();
|
||||
|
||||
if (
|
||||
fromAddr.includes("automated@gusto.com") &&
|
||||
subj.includes("payroll confirmation")
|
||||
) {
|
||||
return "employee";
|
||||
}
|
||||
if (
|
||||
fromAddr.includes("gustonoreply@gusto.com") &&
|
||||
subj.includes("payment confirmation")
|
||||
) {
|
||||
return "contractor";
|
||||
}
|
||||
|
||||
const strippedSubj = subj.replace(/^fwd?:\s*/i, "");
|
||||
const body = (text || "").toLowerCase();
|
||||
const bodyMentionsGusto = body.includes("gusto.com");
|
||||
|
||||
if (bodyMentionsGusto && strippedSubj.includes("payroll confirmation")) {
|
||||
return "employee";
|
||||
}
|
||||
if (bodyMentionsGusto && strippedSubj.includes("payment confirmation")) {
|
||||
return "contractor";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseDateFromSubject(subject) {
|
||||
const m = subject.match(/:\s*(\w+,\s*\w+\s+\d+)\s+(?:payroll|payment)\s+confirmation/i);
|
||||
if (!m) return null;
|
||||
const raw = m[1].trim();
|
||||
const year = new Date().getFullYear();
|
||||
const d = new Date(`${raw}, ${year}`);
|
||||
if (isNaN(d)) return null;
|
||||
return {
|
||||
display: raw,
|
||||
iso: d.toISOString().slice(0, 10),
|
||||
};
|
||||
}
|
||||
|
||||
function parseEmployeePayroll(subject, text) {
|
||||
const checkDate = parseDateFromSubject(subject);
|
||||
if (!checkDate) return null;
|
||||
|
||||
const norm = text.replace(/\n/g, " ").replace(/\s+/g, " ");
|
||||
|
||||
const debitMatch = norm.match(
|
||||
/we.ll debit \$([\d,]+\.\d{2}) from the bank account ending in (\d+)/i
|
||||
);
|
||||
if (!debitMatch) return null;
|
||||
|
||||
const totalDebit = parseFloat(debitMatch[1].replace(/,/g, ""));
|
||||
const bankSuffix = debitMatch[2];
|
||||
|
||||
const payPeriodMatch = norm.match(/payroll for the (.+?)\s+pay period/i);
|
||||
const netPayMatch = norm.match(
|
||||
/\$([\d,]+\.\d{2}) will be for your employee net pay/i
|
||||
);
|
||||
const taxesMatch = norm.match(/\$([\d,]+\.\d{2}) will be for taxes/i);
|
||||
const reimbursementsMatch = norm.match(
|
||||
/\$([\d,]+\.\d{2}) will be for reimbursements/i
|
||||
);
|
||||
|
||||
return {
|
||||
checkDate: checkDate.display,
|
||||
dateKey: checkDate.iso,
|
||||
totalDebit,
|
||||
bankSuffix,
|
||||
payPeriod: payPeriodMatch ? payPeriodMatch[1].trim() : null,
|
||||
netPay: netPayMatch ? parseFloat(netPayMatch[1].replace(/,/g, "")) : null,
|
||||
taxes: taxesMatch ? parseFloat(taxesMatch[1].replace(/,/g, "")) : null,
|
||||
reimbursements: reimbursementsMatch
|
||||
? parseFloat(reimbursementsMatch[1].replace(/,/g, ""))
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
function parseContractorPayment(subject, text) {
|
||||
const checkDate = parseDateFromSubject(subject);
|
||||
if (!checkDate) return null;
|
||||
|
||||
const norm = text.replace(/\n/g, " ").replace(/\s+/g, " ");
|
||||
|
||||
const debitMatch = norm.match(
|
||||
/we.ll debit \$([\d,]+\.\d{2}) from the bank account ending in (\d+)/i
|
||||
);
|
||||
if (!debitMatch) return null;
|
||||
|
||||
const totalDebit = parseFloat(debitMatch[1].replace(/,/g, ""));
|
||||
const bankSuffix = debitMatch[2];
|
||||
|
||||
const nameMatch = norm.match(/at that time for (.+?) to be paid on/i);
|
||||
const contractorName = nameMatch ? nameMatch[1].trim() : "Unknown contractor";
|
||||
|
||||
return {
|
||||
checkDate: checkDate.display,
|
||||
dateKey: checkDate.iso,
|
||||
totalDebit,
|
||||
bankSuffix,
|
||||
contractorName,
|
||||
};
|
||||
}
|
||||
|
||||
function buildCombinedBlocks(dateDisplay, employee, contractors) {
|
||||
const blocks = [
|
||||
{
|
||||
type: "header",
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: `Payroll Processed — ${dateDisplay}`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (employee) {
|
||||
const topFields = [
|
||||
{ type: "mrkdwn", text: `*Check Date*\n${employee.checkDate}` },
|
||||
];
|
||||
if (employee.payPeriod) {
|
||||
topFields.unshift({
|
||||
type: "mrkdwn",
|
||||
text: `*Pay Period*\n${employee.payPeriod}`,
|
||||
});
|
||||
}
|
||||
blocks.push({ type: "section", fields: topFields });
|
||||
blocks.push({ type: "divider" });
|
||||
|
||||
const detailFields = [];
|
||||
if (employee.netPay != null) {
|
||||
detailFields.push({
|
||||
type: "mrkdwn",
|
||||
text: `*Employee Net Pay*\n${formatCurrency(employee.netPay)}`,
|
||||
});
|
||||
}
|
||||
if (employee.taxes != null) {
|
||||
detailFields.push({
|
||||
type: "mrkdwn",
|
||||
text: `*Taxes*\n${formatCurrency(employee.taxes)}`,
|
||||
});
|
||||
}
|
||||
if (detailFields.length) blocks.push({ type: "section", fields: detailFields });
|
||||
|
||||
const extraFields = [];
|
||||
if (employee.reimbursements != null) {
|
||||
extraFields.push({
|
||||
type: "mrkdwn",
|
||||
text: `*Reimbursements*\n${formatCurrency(employee.reimbursements)}`,
|
||||
});
|
||||
}
|
||||
extraFields.push({
|
||||
type: "mrkdwn",
|
||||
text: `*Bank Account*\n...${employee.bankSuffix}`,
|
||||
});
|
||||
blocks.push({ type: "section", fields: extraFields });
|
||||
}
|
||||
|
||||
if (contractors.length > 0) {
|
||||
blocks.push({ type: "divider" });
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Contractor Payments*`,
|
||||
},
|
||||
});
|
||||
for (const c of contractors) {
|
||||
blocks.push({
|
||||
type: "section",
|
||||
fields: [
|
||||
{ type: "mrkdwn", text: `*Contractor*\n${c.contractorName}` },
|
||||
{ type: "mrkdwn", text: `*Amount*\n${formatCurrency(c.totalDebit)}` },
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const grandTotal =
|
||||
(employee ? employee.totalDebit : 0) +
|
||||
contractors.reduce((sum, c) => sum + c.totalDebit, 0);
|
||||
|
||||
blocks.push({ type: "divider" });
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `:moneybag: *Total Bank Debit: ${formatCurrency(grandTotal)}*`,
|
||||
},
|
||||
});
|
||||
blocks.push({
|
||||
type: "context",
|
||||
elements: [
|
||||
{ type: "mrkdwn", text: "Source: Gusto payroll confirmation emails" },
|
||||
],
|
||||
});
|
||||
|
||||
return { blocks, grandTotal };
|
||||
}
|
||||
|
||||
async function postSlack(token, blocks, text) {
|
||||
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ channel: CHANNEL_ID, blocks, text }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
console.error("Slack post failed:", data.error);
|
||||
throw new Error(`Slack API error: ${data.error}`);
|
||||
}
|
||||
return data.ts;
|
||||
}
|
||||
|
||||
const ttl90Days = () => Math.floor(Date.now() / 1000) + 90 * 24 * 60 * 60;
|
||||
|
||||
async function handleS3Event(record) {
|
||||
const bucket = record.s3.bucket.name;
|
||||
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, " "));
|
||||
|
||||
const { Body } = await s3.send(
|
||||
new GetObjectCommand({ Bucket: bucket, Key: key })
|
||||
);
|
||||
const rawEmail = await Body.transformToByteArray();
|
||||
const parsed = await simpleParser(Buffer.from(rawEmail));
|
||||
|
||||
const type = classifyEmail(parsed.from, parsed.subject, parsed.text);
|
||||
if (!type) {
|
||||
console.log("Unrecognized email, skipping:", parsed.subject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "employee") {
|
||||
const data = parseEmployeePayroll(parsed.subject, parsed.text);
|
||||
if (!data) {
|
||||
console.error("Failed to parse employee payroll:", parsed.subject);
|
||||
return;
|
||||
}
|
||||
|
||||
const pk = `PAYROLL_EMAIL#employee#${data.dateKey}`;
|
||||
const existing = await ddb.send(
|
||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk } })
|
||||
);
|
||||
if (existing.Item) {
|
||||
console.log(`Already recorded: employee ${data.dateKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await ddb.send(
|
||||
new PutCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Item: {
|
||||
pk,
|
||||
type: "employee",
|
||||
status: "pending",
|
||||
checkDate: data.checkDate,
|
||||
dateKey: data.dateKey,
|
||||
totalDebit: data.totalDebit,
|
||||
bankSuffix: data.bankSuffix,
|
||||
payPeriod: data.payPeriod,
|
||||
netPay: data.netPay,
|
||||
taxes: data.taxes,
|
||||
reimbursements: data.reimbursements,
|
||||
processedAt: new Date().toISOString(),
|
||||
ttl: ttl90Days(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await sqs.send(
|
||||
new SendMessageCommand({
|
||||
QueueUrl: QUEUE_URL,
|
||||
MessageBody: JSON.stringify({ dateKey: data.dateKey }),
|
||||
})
|
||||
);
|
||||
console.log(`Queued employee payroll for ${data.dateKey}`);
|
||||
}
|
||||
|
||||
if (type === "contractor") {
|
||||
const data = parseContractorPayment(parsed.subject, parsed.text);
|
||||
if (!data) {
|
||||
console.error("Failed to parse contractor payment:", parsed.subject);
|
||||
return;
|
||||
}
|
||||
|
||||
const pk = `PAYROLL_EMAIL#contractor#${data.dateKey}#${data.bankSuffix}`;
|
||||
const existing = await ddb.send(
|
||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk } })
|
||||
);
|
||||
if (existing.Item) {
|
||||
console.log(
|
||||
`Already recorded: contractor ${data.contractorName} ${data.dateKey}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await ddb.send(
|
||||
new PutCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Item: {
|
||||
pk,
|
||||
type: "contractor",
|
||||
status: "pending",
|
||||
checkDate: data.checkDate,
|
||||
dateKey: data.dateKey,
|
||||
totalDebit: data.totalDebit,
|
||||
bankSuffix: data.bankSuffix,
|
||||
contractorName: data.contractorName,
|
||||
processedAt: new Date().toISOString(),
|
||||
ttl: ttl90Days(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await sqs.send(
|
||||
new SendMessageCommand({
|
||||
QueueUrl: QUEUE_URL,
|
||||
MessageBody: JSON.stringify({ dateKey: data.dateKey }),
|
||||
})
|
||||
);
|
||||
console.log(
|
||||
`Queued contractor: ${data.contractorName} for ${data.dateKey}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqsEvent(sqsRecord) {
|
||||
const { dateKey } = JSON.parse(sqsRecord.body);
|
||||
|
||||
const batchPk = `PAYROLL_BATCH#${dateKey}`;
|
||||
const batchCheck = await ddb.send(
|
||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk: batchPk } })
|
||||
);
|
||||
if (batchCheck.Item) {
|
||||
console.log(`Batch already posted for ${dateKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { Items } = await ddb.send(
|
||||
new ScanCommand({
|
||||
TableName: TABLE_NAME,
|
||||
FilterExpression: "begins_with(pk, :prefix) AND #s = :pending",
|
||||
ExpressionAttributeNames: { "#s": "status" },
|
||||
ExpressionAttributeValues: {
|
||||
":prefix": `PAYROLL_EMAIL#`,
|
||||
":pending": "pending",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const pending = (Items || []).filter((i) => i.dateKey === dateKey);
|
||||
if (pending.length === 0) {
|
||||
console.log(`No pending items for ${dateKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const employee = pending.find((i) => i.type === "employee") || null;
|
||||
const contractors = pending.filter((i) => i.type === "contractor");
|
||||
const dateDisplay = pending[0].checkDate;
|
||||
|
||||
const token = await getSlackToken();
|
||||
const { blocks, grandTotal } = buildCombinedBlocks(dateDisplay, employee, contractors);
|
||||
const slackTs = await postSlack(
|
||||
token,
|
||||
blocks,
|
||||
`Payroll processed for ${dateDisplay}: ${formatCurrency(grandTotal)}`
|
||||
);
|
||||
|
||||
await ddb.send(
|
||||
new PutCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Item: {
|
||||
pk: batchPk,
|
||||
dateKey,
|
||||
checkDate: dateDisplay,
|
||||
employeeCount: employee ? 1 : 0,
|
||||
contractorCount: contractors.length,
|
||||
totalDebit: grandTotal,
|
||||
slackTs,
|
||||
processedAt: new Date().toISOString(),
|
||||
ttl: ttl90Days(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const updateBatch = pending.map((item) => ({
|
||||
PutRequest: {
|
||||
Item: { ...item, status: "notified" },
|
||||
},
|
||||
}));
|
||||
for (let i = 0; i < updateBatch.length; i += 25) {
|
||||
await ddb.send(
|
||||
new BatchWriteCommand({
|
||||
RequestItems: { [TABLE_NAME]: updateBatch.slice(i, i + 25) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Posted batch: ${employee ? 1 : 0} employee + ${contractors.length} contractor(s) for ${dateKey}`
|
||||
);
|
||||
}
|
||||
|
||||
export const handler = async (event) => {
|
||||
if (event.Records?.[0]?.eventSource === "aws:s3") {
|
||||
for (const record of event.Records) {
|
||||
await handleS3Event(record);
|
||||
}
|
||||
} else if (event.Records?.[0]?.eventSource === "aws:sqs") {
|
||||
for (const record of event.Records) {
|
||||
await handleSqsEvent(record);
|
||||
}
|
||||
} else {
|
||||
console.log("Unknown event source:", JSON.stringify(event).slice(0, 200));
|
||||
}
|
||||
|
||||
return { statusCode: 200 };
|
||||
};
|
||||
215
template.yaml
215
template.yaml
|
|
@ -82,23 +82,6 @@ Resources:
|
|||
SubnetId: !Ref PublicSubnet
|
||||
RouteTableId: !Ref PublicRouteTable
|
||||
|
||||
PublicSubnetB:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
VpcId: !Ref Vpc
|
||||
CidrBlock: 10.20.3.0/24
|
||||
AvailabilityZone: !Select [1, !GetAZs ""]
|
||||
MapPublicIpOnLaunch: true
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: payments-dashboard-public-b
|
||||
|
||||
PublicSubnetBRouteTableAssociation:
|
||||
Type: AWS::EC2::SubnetRouteTableAssociation
|
||||
Properties:
|
||||
SubnetId: !Ref PublicSubnetB
|
||||
RouteTableId: !Ref PublicRouteTable
|
||||
|
||||
PrivateRouteTable:
|
||||
Type: AWS::EC2::RouteTable
|
||||
Properties:
|
||||
|
|
@ -126,55 +109,6 @@ Resources:
|
|||
- IpProtocol: "-1"
|
||||
CidrIp: 0.0.0.0/0
|
||||
|
||||
# Aurora Serverless v2 PostgreSQL (Dataddo → payroll data)
|
||||
AuroraSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: Aurora PostgreSQL access
|
||||
VpcId: !Ref Vpc
|
||||
SecurityGroupIngress:
|
||||
- IpProtocol: tcp
|
||||
FromPort: 5432
|
||||
ToPort: 5432
|
||||
SourceSecurityGroupId: !Ref LambdaSecurityGroup
|
||||
- IpProtocol: tcp
|
||||
FromPort: 5432
|
||||
ToPort: 5432
|
||||
CidrIp: 0.0.0.0/0
|
||||
|
||||
AuroraSubnetGroup:
|
||||
Type: AWS::RDS::DBSubnetGroup
|
||||
Properties:
|
||||
DBSubnetGroupDescription: Public subnets for Aurora PostgreSQL
|
||||
SubnetIds:
|
||||
- !Ref PublicSubnet
|
||||
- !Ref PublicSubnetB
|
||||
|
||||
AuroraCluster:
|
||||
Type: AWS::RDS::DBCluster
|
||||
Properties:
|
||||
Engine: aurora-postgresql
|
||||
EngineVersion: "16.4"
|
||||
DatabaseName: payroll
|
||||
MasterUsername: payroll_admin
|
||||
ManageMasterUserPassword: true
|
||||
ServerlessV2ScalingConfiguration:
|
||||
MinCapacity: 0.5
|
||||
MaxCapacity: 2
|
||||
VpcSecurityGroupIds:
|
||||
- !Ref AuroraSecurityGroup
|
||||
DBSubnetGroupName: !Ref AuroraSubnetGroup
|
||||
EnableHttpEndpoint: true
|
||||
StorageEncrypted: true
|
||||
|
||||
AuroraInstance:
|
||||
Type: AWS::RDS::DBInstance
|
||||
Properties:
|
||||
DBClusterIdentifier: !Ref AuroraCluster
|
||||
DBInstanceClass: db.serverless
|
||||
Engine: aurora-postgresql
|
||||
PubliclyAccessible: true
|
||||
|
||||
PaymentsCsvBucket:
|
||||
Type: AWS::S3::Bucket
|
||||
Properties:
|
||||
|
|
@ -195,6 +129,98 @@ Resources:
|
|||
AttributeName: ttl
|
||||
Enabled: true
|
||||
|
||||
# Payroll email ingestion (replaces Dataddo/Aurora pipeline)
|
||||
PayrollEmailBucket:
|
||||
Type: AWS::S3::Bucket
|
||||
Properties:
|
||||
BucketName: !Sub seahaven-payroll-emails-${AWS::AccountId}
|
||||
LifecycleConfiguration:
|
||||
Rules:
|
||||
- Id: ExpireEmails
|
||||
Status: Enabled
|
||||
ExpirationInDays: 30
|
||||
|
||||
PayrollEmailBucketPolicy:
|
||||
Type: AWS::S3::BucketPolicy
|
||||
Properties:
|
||||
Bucket: !Ref PayrollEmailBucket
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Sid: AllowSESPut
|
||||
Effect: Allow
|
||||
Principal:
|
||||
Service: ses.amazonaws.com
|
||||
Action: s3:PutObject
|
||||
Resource: !Sub arn:aws:s3:::seahaven-payroll-emails-${AWS::AccountId}/*
|
||||
Condition:
|
||||
StringEquals:
|
||||
AWS:SourceAccount: !Ref AWS::AccountId
|
||||
|
||||
PayrollEmailRule:
|
||||
Type: AWS::SES::ReceiptRule
|
||||
DependsOn: PayrollEmailBucketPolicy
|
||||
Properties:
|
||||
RuleSetName: INBOUND_MAIL
|
||||
After: ExistingRuleSetWorkorderEmailRuleEA29F845-okepxcVarTfu
|
||||
Rule:
|
||||
Name: store-payroll-emails
|
||||
Enabled: true
|
||||
ScanEnabled: true
|
||||
Recipients:
|
||||
- payroll@int.seahaven.com
|
||||
Actions:
|
||||
- S3Action:
|
||||
BucketName: !Ref PayrollEmailBucket
|
||||
ObjectKeyPrefix: inbound/
|
||||
|
||||
ContractorBatchQueue:
|
||||
Type: AWS::SQS::Queue
|
||||
Properties:
|
||||
QueueName: payments-contractor-batch
|
||||
DelaySeconds: 600
|
||||
MessageRetentionPeriod: 86400
|
||||
VisibilityTimeout: 60
|
||||
|
||||
ProcessPayrollEmailFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
FunctionName: payments-processPayrollEmail
|
||||
Handler: src/processPayrollEmail.handler
|
||||
Timeout: 60
|
||||
Environment:
|
||||
Variables:
|
||||
SLACK_BOT_TOKEN_PARAM: /payments-dashboard/slack-bot-token
|
||||
PAYROLL_CHANNEL_ID: C0AV5RBMYKU
|
||||
CONTRACTOR_BATCH_QUEUE_URL: !Ref ContractorBatchQueue
|
||||
Events:
|
||||
EmailReceived:
|
||||
Type: S3
|
||||
Properties:
|
||||
Bucket: !Ref PayrollEmailBucket
|
||||
Events: s3:ObjectCreated:*
|
||||
Filter:
|
||||
S3Key:
|
||||
Rules:
|
||||
- Name: prefix
|
||||
Value: inbound/
|
||||
ContractorBatch:
|
||||
Type: SQS
|
||||
Properties:
|
||||
Queue: !GetAtt ContractorBatchQueue.Arn
|
||||
BatchSize: 1
|
||||
Policies:
|
||||
- S3ReadPolicy:
|
||||
BucketName: !Sub seahaven-payroll-emails-${AWS::AccountId}
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref DashboardTable
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/slack-bot-token
|
||||
- SQSSendMessagePolicy:
|
||||
QueueName: !GetAtt ContractorBatchQueue.QueueName
|
||||
- SQSPollerPolicy:
|
||||
QueueName: !GetAtt ContractorBatchQueue.QueueName
|
||||
|
||||
ProcessPaymentCsvFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
|
|
@ -330,51 +356,6 @@ Resources:
|
|||
- ec2:DeleteNetworkInterface
|
||||
Resource: "*"
|
||||
|
||||
NotifyPayrollFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
FunctionName: payments-notifyPayroll
|
||||
Handler: src/notifyPayroll.handler
|
||||
Timeout: 60
|
||||
VpcConfig:
|
||||
SubnetIds:
|
||||
- !Ref PrivateSubnet
|
||||
SecurityGroupIds:
|
||||
- !Ref LambdaSecurityGroup
|
||||
Environment:
|
||||
Variables:
|
||||
AURORA_CLUSTER_ARN: !GetAtt AuroraCluster.DBClusterArn
|
||||
AURORA_SECRET_ARN: !GetAtt AuroraCluster.MasterUserSecret.SecretArn
|
||||
SLACK_BOT_TOKEN_PARAM: /payments-dashboard/slack-bot-token
|
||||
Events:
|
||||
PayrollCheck:
|
||||
Type: Schedule
|
||||
Properties:
|
||||
Schedule: cron(0 18 ? * MON-FRI *)
|
||||
Description: Check for new payroll data at 2pm ET (18:00 UTC)
|
||||
Enabled: true
|
||||
Policies:
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref DashboardTable
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/slack-bot-token
|
||||
- Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ec2:CreateNetworkInterface
|
||||
- ec2:DescribeNetworkInterfaces
|
||||
- ec2:DeleteNetworkInterface
|
||||
Resource: "*"
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- rds-data:ExecuteStatement
|
||||
Resource: !GetAtt AuroraCluster.DBClusterArn
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:GetSecretValue
|
||||
Resource: !GetAtt AuroraCluster.MasterUserSecret.SecretArn
|
||||
|
||||
Outputs:
|
||||
SlackEventUrl:
|
||||
Description: URL to set as the Slack app Request URL
|
||||
|
|
@ -385,12 +366,6 @@ Outputs:
|
|||
StaticOutboundIp:
|
||||
Description: Static IP for BoA API whitelist
|
||||
Value: !Ref NatEip
|
||||
AuroraEndpoint:
|
||||
Description: Aurora PostgreSQL cluster endpoint
|
||||
Value: !GetAtt AuroraCluster.Endpoint.Address
|
||||
AuroraPort:
|
||||
Description: Aurora PostgreSQL port
|
||||
Value: !GetAtt AuroraCluster.Endpoint.Port
|
||||
AuroraSecretArn:
|
||||
Description: Secrets Manager ARN for Aurora master credentials
|
||||
Value: !GetAtt AuroraCluster.MasterUserSecret.SecretArn
|
||||
PayrollEmailBucket:
|
||||
Description: S3 bucket for inbound payroll emails from SES
|
||||
Value: !Ref PayrollEmailBucket
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue