diff --git a/.env.prod.example b/.env.prod.example index c18193c..269950f 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -2,3 +2,7 @@ TZ="Pacific/Auckland" PROJECTWORKS_USERNAME=test PROJECTWORKS_PASSWORD=test SLACK_WEBHOOK_URL=test + +CONTENTFUL_SPACE_ID=test +CONTENTFUL_API_KEY=test +SLACK_API_TOKEN=test diff --git a/.env.staging.example b/.env.staging.example index c18193c..269950f 100644 --- a/.env.staging.example +++ b/.env.staging.example @@ -2,3 +2,7 @@ TZ="Pacific/Auckland" PROJECTWORKS_USERNAME=test PROJECTWORKS_PASSWORD=test SLACK_WEBHOOK_URL=test + +CONTENTFUL_SPACE_ID=test +CONTENTFUL_API_KEY=test +SLACK_API_TOKEN=test diff --git a/package-lock.json b/package-lock.json index 6f0f172..eb28dcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@slack/web-api": "^6.11.0", "@slack/webhook": "^7.0.1", + "contentful": "^10.6.14", "luxon": "^3.4.4", "node-fetch": "^2.6.7", "node-ical": "^0.17.1" @@ -1435,6 +1437,14 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@contentful/rich-text-types": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@contentful/rich-text-types/-/rich-text-types-16.3.0.tgz", + "integrity": "sha512-OfQmAu5bxE0CgQA3WlUleVej+ifFG/iXmB2DmUl4EyWyFue1aiIvfjxQhcDRSH4n1jUNMJ6L1wInZL8uV5m3TQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2309,6 +2319,18 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@slack/logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-3.0.0.tgz", + "integrity": "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==", + "dependencies": { + "@types/node": ">=12.0.0" + }, + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, "node_modules/@slack/types": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.11.0.tgz", @@ -2318,6 +2340,49 @@ "npm": ">= 6.12.0" } }, + "node_modules/@slack/web-api": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.11.0.tgz", + "integrity": "sha512-DLShYUc2dE8QrhmrJZ7YuhAuh/VW88Dt3LL7s2KrkEOyxbdfjAtF9bzPOBMlXuWmO6nWLGVevPuhShcYyjwTEw==", + "dependencies": { + "@slack/logger": "^3.0.0", + "@slack/types": "^2.11.0", + "@types/is-stream": "^1.1.0", + "@types/node": ">=12.0.0", + "axios": "^1.6.0", + "eventemitter3": "^3.1.0", + "form-data": "^2.5.0", + "is-electron": "2.2.2", + "is-stream": "^1.1.0", + "p-queue": "^6.6.1", + "p-retry": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@slack/web-api/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@slack/webhook": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@slack/webhook/-/webhook-7.0.1.tgz", @@ -3288,6 +3353,14 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, + "node_modules/@types/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "dev": true, @@ -3389,6 +3462,11 @@ "@types/node": "*" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", @@ -4800,7 +4878,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2", "get-intrinsic": "^1.2.1", @@ -5269,6 +5346,59 @@ "node": ">= 0.6" } }, + "node_modules/contentful": { + "version": "10.6.14", + "resolved": "https://registry.npmjs.org/contentful/-/contentful-10.6.14.tgz", + "integrity": "sha512-FbdT+ApX7W5r2OZ/6GfKRls8BjrVtJBl/0ijz9ZdzZ7jpZavyQTo9JshSWGk7aJAs8vFGGDkrpaWO1gxYdi/tg==", + "dependencies": { + "@contentful/rich-text-types": "^16.0.2", + "axios": "^1.6.0", + "contentful-resolve-response": "^1.8.1", + "contentful-sdk-core": "^8.1.0", + "json-stringify-safe": "^5.0.1", + "type-fest": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/contentful-resolve-response": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/contentful-resolve-response/-/contentful-resolve-response-1.8.1.tgz", + "integrity": "sha512-VXGK2c8dBIGcRCknqudKmkDr2PzsUYfjLN6hhx71T09UzoXOdA/c0kfDhsf/BBCBWPWcLaUgaJEFU0lCo45TSg==", + "dependencies": { + "fast-copy": "^2.1.7" + }, + "engines": { + "node": ">=4.7.2" + } + }, + "node_modules/contentful-sdk-core": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-8.1.2.tgz", + "integrity": "sha512-XZvX2JMJF4YiICXLrHFv59KBHaQJ6ElqAP8gSNgnCu4x+pPG7Y1bC2JMNOiyAgJuGQGVUOcNZ5PmK+tsNEayYw==", + "dependencies": { + "fast-copy": "^2.1.7", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "p-throttle": "^4.1.1", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/contentful/node_modules/type-fest": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.8.3.tgz", + "integrity": "sha512-//BaTm14Q/gHBn09xlnKNqfI8t6bmdzx2DXYfPBNofN0WUybCEUDcbCWcTa0oF09lzLjZgPphXAsvRiMK0V6Bw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5861,7 +5991,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.1", "gopd": "^1.0.1", @@ -6834,6 +6963,11 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, "node_modules/events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -6939,6 +7073,11 @@ "node": ">=4" } }, + "node_modules/fast-copy": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz", + "integrity": "sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7322,7 +7461,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7376,7 +7514,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, "dependencies": { "function-bind": "^1.1.2", "has-proto": "^1.0.1", @@ -7522,7 +7659,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -7595,7 +7731,6 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.0", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.1" @@ -7608,7 +7743,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7618,7 +7752,6 @@ }, "node_modules/has-symbols": { "version": "1.0.3", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7645,7 +7778,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -8015,6 +8147,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, "node_modules/is-extglob": { "version": "2.1.1", "dev": true, @@ -9126,6 +9263,11 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -9340,8 +9482,12 @@ "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -9856,7 +10002,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10051,7 +10196,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, "engines": { "node": ">=4" } @@ -10097,11 +10241,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-4.1.1.tgz", + "integrity": "sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, "dependencies": { "p-finally": "^1.0.0" }, @@ -10437,10 +10623,9 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dependencies": { "side-channel": "^1.0.4" }, @@ -10702,6 +10887,14 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "dev": true, @@ -11045,7 +11238,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, "dependencies": { "define-data-property": "^1.1.1", "get-intrinsic": "^1.2.1", @@ -11097,7 +11289,6 @@ }, "node_modules/side-channel": { "version": "1.0.4", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.0", @@ -13682,6 +13873,11 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@contentful/rich-text-types": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@contentful/rich-text-types/-/rich-text-types-16.3.0.tgz", + "integrity": "sha512-OfQmAu5bxE0CgQA3WlUleVej+ifFG/iXmB2DmUl4EyWyFue1aiIvfjxQhcDRSH4n1jUNMJ6L1wInZL8uV5m3TQ==" + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -14384,11 +14580,54 @@ "@sinonjs/commons": "^3.0.0" } }, + "@slack/logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-3.0.0.tgz", + "integrity": "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==", + "requires": { + "@types/node": ">=12.0.0" + } + }, "@slack/types": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.11.0.tgz", "integrity": "sha512-UlIrDWvuLaDly3QZhCPnwUSI/KYmV1N9LyhuH6EDKCRS1HWZhyTG3Ja46T3D0rYfqdltKYFXbJSSRPwZpwO0cQ==" }, + "@slack/web-api": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.11.0.tgz", + "integrity": "sha512-DLShYUc2dE8QrhmrJZ7YuhAuh/VW88Dt3LL7s2KrkEOyxbdfjAtF9bzPOBMlXuWmO6nWLGVevPuhShcYyjwTEw==", + "requires": { + "@slack/logger": "^3.0.0", + "@slack/types": "^2.11.0", + "@types/is-stream": "^1.1.0", + "@types/node": ">=12.0.0", + "axios": "^1.6.0", + "eventemitter3": "^3.1.0", + "form-data": "^2.5.0", + "is-electron": "2.2.2", + "is-stream": "^1.1.0", + "p-queue": "^6.6.1", + "p-retry": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + } + } + }, "@slack/webhook": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@slack/webhook/-/webhook-7.0.1.tgz", @@ -15320,6 +15559,14 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, + "@types/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==", + "requires": { + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "dev": true @@ -15417,6 +15664,11 @@ "@types/node": "*" } }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", @@ -16385,7 +16637,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, "requires": { "function-bind": "^1.1.2", "get-intrinsic": "^1.2.1", @@ -16726,6 +16977,46 @@ "safe-buffer": "5.2.1" } }, + "contentful": { + "version": "10.6.14", + "resolved": "https://registry.npmjs.org/contentful/-/contentful-10.6.14.tgz", + "integrity": "sha512-FbdT+ApX7W5r2OZ/6GfKRls8BjrVtJBl/0ijz9ZdzZ7jpZavyQTo9JshSWGk7aJAs8vFGGDkrpaWO1gxYdi/tg==", + "requires": { + "@contentful/rich-text-types": "^16.0.2", + "axios": "^1.6.0", + "contentful-resolve-response": "^1.8.1", + "contentful-sdk-core": "^8.1.0", + "json-stringify-safe": "^5.0.1", + "type-fest": "^4.0.0" + }, + "dependencies": { + "type-fest": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.8.3.tgz", + "integrity": "sha512-//BaTm14Q/gHBn09xlnKNqfI8t6bmdzx2DXYfPBNofN0WUybCEUDcbCWcTa0oF09lzLjZgPphXAsvRiMK0V6Bw==" + } + } + }, + "contentful-resolve-response": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/contentful-resolve-response/-/contentful-resolve-response-1.8.1.tgz", + "integrity": "sha512-VXGK2c8dBIGcRCknqudKmkDr2PzsUYfjLN6hhx71T09UzoXOdA/c0kfDhsf/BBCBWPWcLaUgaJEFU0lCo45TSg==", + "requires": { + "fast-copy": "^2.1.7" + } + }, + "contentful-sdk-core": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-8.1.2.tgz", + "integrity": "sha512-XZvX2JMJF4YiICXLrHFv59KBHaQJ6ElqAP8gSNgnCu4x+pPG7Y1bC2JMNOiyAgJuGQGVUOcNZ5PmK+tsNEayYw==", + "requires": { + "fast-copy": "^2.1.7", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "p-throttle": "^4.1.1", + "qs": "^6.11.2" + } + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -17175,7 +17466,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, "requires": { "get-intrinsic": "^1.2.1", "gopd": "^1.0.1", @@ -17839,6 +18129,11 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -17920,6 +18215,11 @@ "tmp": "^0.0.33" } }, + "fast-copy": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz", + "integrity": "sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -18196,8 +18496,7 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.6", @@ -18233,7 +18532,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, "requires": { "function-bind": "^1.1.2", "has-proto": "^1.0.1", @@ -18328,7 +18626,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -18385,7 +18682,6 @@ }, "has-property-descriptors": { "version": "1.0.0", - "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -18393,12 +18689,10 @@ "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { - "version": "1.0.3", - "dev": true + "version": "1.0.3" }, "has-tostringtag": { "version": "1.0.0", @@ -18411,7 +18705,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, "requires": { "function-bind": "^1.1.2" } @@ -18677,6 +18970,11 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, + "is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, "is-extglob": { "version": "2.1.1", "dev": true @@ -19500,6 +19798,11 @@ "version": "1.0.1", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, "json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -19698,8 +20001,12 @@ "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "lodash.memoize": { "version": "4.1.2", @@ -20087,8 +20394,7 @@ "object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-keys": { "version": "1.1.1", @@ -20224,8 +20530,7 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" }, "p-limit": { "version": "3.1.0", @@ -20254,11 +20559,40 @@ } } }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + } + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "p-throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-4.1.1.tgz", + "integrity": "sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==" + }, "p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, "requires": { "p-finally": "^1.0.0" } @@ -20491,10 +20825,9 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "requires": { "side-channel": "^1.0.4" } @@ -20669,6 +21002,11 @@ "signal-exit": "^3.0.2" } }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, "reusify": { "version": "1.0.4", "dev": true @@ -20921,7 +21259,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, "requires": { "define-data-property": "^1.1.1", "get-intrinsic": "^1.2.1", @@ -20959,7 +21296,6 @@ }, "side-channel": { "version": "1.0.4", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", diff --git a/package.json b/package.json index 8642447..6b37d74 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "author": "Ackama Developers ", "license": "MIT", "dependencies": { + "@slack/web-api": "^6.11.0", "@slack/webhook": "^7.0.1", + "contentful": "^10.6.14", "luxon": "^3.4.4", "node-fetch": "^2.6.7", "node-ical": "^0.17.1" diff --git a/src/handlers.ts b/src/handlers.ts index 42a47ea..daabd2b 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -3,6 +3,7 @@ import * as daylightSavingsChangeReminder from './daylightSavingsChangeReminder' import * as leaveReminder from './leaveReminder'; import { Notifier } from './notifier'; import * as publicHolidayReminder from './publicHolidayReminder'; +import * as significantStaffDatesReminder from './significantStaffDatesReminder'; export const weeklyReport: ScheduledHandler = async () => { const notifier = new Notifier(); @@ -17,6 +18,7 @@ export const dailyReport: ScheduledHandler = async () => { await leaveReminder.generateDailyReport(notifier); await publicHolidayReminder.generateDailyReport(notifier); daylightSavingsChangeReminder.generateDailyReport(notifier); + await significantStaffDatesReminder.generateDailyReport(notifier); await notifier.sendBufferedMessages(); }; diff --git a/src/significantStaffDatesReminder.ts b/src/significantStaffDatesReminder.ts new file mode 100644 index 0000000..b10d2ae --- /dev/null +++ b/src/significantStaffDatesReminder.ts @@ -0,0 +1,183 @@ +import { WebClient } from '@slack/web-api'; +import * as contentful from 'contentful'; +import { DateTime } from 'luxon'; +import { Notifier } from './notifier'; + +declare global { + namespace NodeJS { + interface ProcessEnv { + CONTENTFUL_SPACE_ID: string; + CONTENTFUL_API_KEY: string; + SLACK_API_TOKEN: string; + } + } +} + +interface ContentfulStaffMember { + name: string; + slackHandle: string; + bornOn: string; + startedOn: string; + birthdayNotificationEnabled: boolean; + workAnniversaryNotificationEnabled: boolean; +} + +interface Member { + id?: string; + real_name?: string; + profile: { + display_name?: string; + }; +} + +const CONTENTFUL_SPACE_ID = process.env.CONTENTFUL_SPACE_ID; +const CONTENTFUL_API_KEY = process.env.CONTENTFUL_API_KEY; +const SLACK_API_TOKEN = process.env.SLACK_API_TOKEN; + +// Slack has no API to lookup a single user by name so we have to get all +// users via `cacheSlackUsers` below. This is an expensive operation both in +// latency and also Slack rate-limits it so we cache the results here. +let allSlackUsers: Member[] = []; + +const cacheSlackUsers = async (): Promise => { + if (allSlackUsers.length > 0) { + return; + } + + const client = new WebClient(SLACK_API_TOKEN, { + retryConfig: { retries: 0 } + }); + + try { + // Docs: https://api.slack.com/methods/users.list + const response = await client.users.list(); + + allSlackUsers = response.members as Member[]; + } catch (error: unknown) { + console.log('Failed to get list of users from Slack. Error:', error); + } +}; + +const getNameFromSlack = (slackHandle: string): string => { + // user.profile.display_name is what you seen in slack as the user's slack + // name e.g. `user.profile.display_name === 'Riker'` means you see `@Riker` in + // Slack. + const user = allSlackUsers.find(u => u.profile.display_name === slackHandle); + + return `${user?.real_name} <@${user?.id}>`; +}; + +const hasUpcomingWorkAnniversary = (person: ContentfulStaffMember) => { + return ( + person.workAnniversaryNotificationEnabled && + yearlyAnniversaryIsCloseEnoughToToday(person.startedOn) + ); +}; + +const numYearsAgo = (dateStr: string): number => { + const now = DateTime.now().year; + const then = DateTime.fromISO(dateStr).year; + + return now - then; +}; + +const prettyDate = (dateStr: string): string => { + return DateTime.fromISO(dateStr).toFormat('d LLLL'); +}; + +const buildWorkAnniversaryMessage = (person: ContentfulStaffMember): string => { + const tenure = numYearsAgo(person.startedOn); + const name = getNameFromSlack(person.slackHandle); + const date = prettyDate(person.startedOn); + const message = `:tada: Congratulations ${name} - as of ${date}, you will have worked at Ackama for ${tenure} years! :tada:`; + + return message; +}; + +const buildBirthdayMessage = (person: ContentfulStaffMember): string => { + const age = numYearsAgo(person.bornOn); + const name = getNameFromSlack(person.slackHandle); + const date = prettyDate(person.bornOn); + const message = `:birthday: Happy birthday to ${name} who is turning ${age} on ${date}! :birthday:`; + + return message; +}; + +// Is the yearly anniversary of the given date happening today or one of the +// next two days (if today is Friday). +const yearlyAnniversaryIsCloseEnoughToToday = (dateStr: string): boolean => { + // Use NZ timezone. Melbourne is only 2hrs behind so this should give the + // correct date there too. + const todayInNZ = DateTime.local().setZone('Pacific/Auckland'); + + const daysOfInterest = [todayInNZ.toFormat('MM-dd')]; // always check today + + // if today is Friday then we check the upcoming weekend days too + if (todayInNZ.weekdayShort === 'Fri') { + daysOfInterest.concat(todayInNZ.plus({ days: 1 }).toFormat('MM-dd')); // Saturday + daysOfInterest.concat(todayInNZ.plus({ days: 2 }).toFormat('MM-dd')); // Sunday + } + + const query = dateStr.replace(/^\d{4}-/u, ''); + + // check if today is a day of interest + if (!daysOfInterest.includes(query)) { + return false; + } + + return true; +}; + +const hasUpcomingBirthday = (person: ContentfulStaffMember) => { + return ( + person.birthdayNotificationEnabled && + yearlyAnniversaryIsCloseEnoughToToday(person.bornOn) + ); +}; + +const fetchStaffMembersFromContentful = async (): Promise< + ContentfulStaffMember[] +> => { + const client = contentful.createClient({ + space: CONTENTFUL_SPACE_ID, + accessToken: CONTENTFUL_API_KEY + }); + const staffMemberEntries = await client.getEntries({ + content_type: 'staffMember' + }); + + return staffMemberEntries.items.map( + entry => entry.fields as unknown as ContentfulStaffMember + ); +}; + +const sendBirthdayNotifications = ( + staffMembers: ContentfulStaffMember[], + notifier: Notifier +): void => { + staffMembers.filter(hasUpcomingBirthday).forEach(staffMember => { + const msg = buildBirthdayMessage(staffMember); + + notifier.bufferMessage(msg, 'section'); + }); +}; + +const sendWorkAnniversaryNotifications = ( + staffMembers: ContentfulStaffMember[], + notifier: Notifier +): void => { + staffMembers.filter(hasUpcomingWorkAnniversary).forEach(staffMember => { + const msg = buildWorkAnniversaryMessage(staffMember); + + notifier.bufferMessage(msg, 'section'); + }); +}; + +export async function generateDailyReport(notifier: Notifier): Promise { + await cacheSlackUsers(); + + const staffMembers = await fetchStaffMembersFromContentful(); + + sendBirthdayNotifications(staffMembers, notifier); + sendWorkAnniversaryNotifications(staffMembers, notifier); +}