@seeka-labs/cli-apps 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +1 -0
  3. package/dist/index.js +47 -0
  4. package/dist/init-templates/aws-lambda/.env.example +15 -0
  5. package/dist/init-templates/aws-lambda/.eslintrc.cjs +10 -0
  6. package/dist/init-templates/aws-lambda/.example.gitignore +49 -0
  7. package/dist/init-templates/aws-lambda/.gitlab-ci.yml +37 -0
  8. package/dist/init-templates/aws-lambda/.vscode/extensions.json +5 -0
  9. package/dist/init-templates/aws-lambda/.vscode/launch.json +20 -0
  10. package/dist/init-templates/aws-lambda/.vscode/settings.json +3 -0
  11. package/dist/init-templates/aws-lambda/.vscode/tasks.json +12 -0
  12. package/dist/init-templates/aws-lambda/README.md +75 -0
  13. package/dist/init-templates/aws-lambda/package.json +51 -0
  14. package/dist/init-templates/aws-lambda/scripts/ngrok.js +28 -0
  15. package/dist/init-templates/aws-lambda/src/index.ts +33 -0
  16. package/dist/init-templates/aws-lambda/src/lib/logging/index.ts +88 -0
  17. package/dist/init-templates/aws-lambda/src/lib/services/index.ts +41 -0
  18. package/dist/init-templates/aws-lambda/src/lib/state/redis/index.ts +64 -0
  19. package/dist/init-templates/aws-lambda/src/lib/state/seeka/installations.ts +67 -0
  20. package/dist/init-templates/aws-lambda/src/routes/seekaAppWebhook.ts +170 -0
  21. package/dist/init-templates/aws-lambda/tsconfig.json +31 -0
  22. package/dist/init-templates/azure-function/.eslintrc.cjs +10 -0
  23. package/dist/init-templates/azure-function/.example.gitignore +48 -0
  24. package/dist/init-templates/azure-function/.funcignore +17 -0
  25. package/dist/init-templates/azure-function/.gitlab-ci.yml +33 -0
  26. package/dist/init-templates/azure-function/.vscode/extensions.json +7 -0
  27. package/dist/init-templates/azure-function/.vscode/launch.json +13 -0
  28. package/dist/init-templates/azure-function/.vscode/settings.json +9 -0
  29. package/dist/init-templates/azure-function/.vscode/tasks.json +39 -0
  30. package/dist/init-templates/azure-function/README.md +102 -0
  31. package/dist/init-templates/azure-function/host.json +20 -0
  32. package/dist/init-templates/azure-function/local.settings.example.json +23 -0
  33. package/dist/init-templates/azure-function/package.json +44 -0
  34. package/dist/init-templates/azure-function/scripts/ngrok.js +28 -0
  35. package/dist/init-templates/azure-function/src/functions/pollingExample.ts +39 -0
  36. package/dist/init-templates/azure-function/src/functions/queueExample.ts +33 -0
  37. package/dist/init-templates/azure-function/src/functions/seekaAppWebhook.ts +200 -0
  38. package/dist/init-templates/azure-function/src/lib/jobs/index.ts +54 -0
  39. package/dist/init-templates/azure-function/src/lib/logging/index.ts +93 -0
  40. package/dist/init-templates/azure-function/src/lib/services/index.ts +41 -0
  41. package/dist/init-templates/azure-function/src/lib/state/redis/index.ts +64 -0
  42. package/dist/init-templates/azure-function/src/lib/state/seeka/installations.ts +67 -0
  43. package/dist/init-templates/azure-function/tsconfig.json +22 -0
  44. package/dist/init-templates/netlify-function/.env.example +18 -0
  45. package/dist/init-templates/netlify-function/.eslintrc.cjs +7 -0
  46. package/dist/init-templates/netlify-function/.example.gitignore +36 -0
  47. package/dist/init-templates/netlify-function/.vscode/launch.json +45 -0
  48. package/dist/init-templates/netlify-function/README.md +60 -0
  49. package/dist/init-templates/netlify-function/netlify.toml +7 -0
  50. package/dist/init-templates/netlify-function/package.json +38 -0
  51. package/dist/init-templates/netlify-function/src/api/example-job-background/index.ts +52 -0
  52. package/dist/init-templates/netlify-function/src/api/polling-example-job-scheduled/index.ts +46 -0
  53. package/dist/init-templates/netlify-function/src/api/seeka-app-webhook/index.ts +185 -0
  54. package/dist/init-templates/netlify-function/src/lib/jobs/index.ts +68 -0
  55. package/dist/init-templates/netlify-function/src/lib/logging/index.ts +91 -0
  56. package/dist/init-templates/netlify-function/src/lib/services/index.ts +41 -0
  57. package/dist/init-templates/netlify-function/src/lib/state/redis/index.ts +64 -0
  58. package/dist/init-templates/netlify-function/src/lib/state/seeka/installations.ts +67 -0
  59. package/dist/init-templates/netlify-function/tsconfig.json +25 -0
  60. package/package.json +48 -0
@@ -0,0 +1,41 @@
1
+ import winston, { Logger } from 'winston';
2
+
3
+ import { connect, disconnect, isConnected } from '../state/redis';
4
+
5
+ export const startServices = async (logger: Logger) => {
6
+ logger.debug(`Trying to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`)
7
+ try {
8
+ if (isConnected()) {
9
+ logger.verbose(`Redis already connected - ${process.env.REDIS_CONNECTION_HOST}`)
10
+ }
11
+ else {
12
+ logger.profile('service.redis.connect')
13
+ await connect();
14
+ logger.profile('service.redis.connect')
15
+ logger.debug(`Redis connected - ${process.env.REDIS_CONNECTION_HOST}`)
16
+ }
17
+ }
18
+ catch (err) {
19
+ logger.error(`Failed to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
20
+ throw err;
21
+ }
22
+ }
23
+
24
+ export const stopServices = async (logger: Logger) => {
25
+ logger.debug(`Trying to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`)
26
+ try {
27
+ if (isConnected() === false) {
28
+ logger.verbose(`Redis already disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
29
+ }
30
+ else {
31
+ logger.profile('service.redis.disconnect')
32
+ await disconnect();
33
+ logger.profile('service.redis.disconnect')
34
+ logger.verbose(`Redis disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
35
+ }
36
+ }
37
+ catch (err) {
38
+ logger.error(`Failed to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
39
+ throw err;
40
+ }
41
+ }
@@ -0,0 +1,64 @@
1
+ import { createClient } from 'redis';
2
+
3
+ import { logger } from '../../logging';
4
+
5
+ const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
6
+ const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${process.env.REDIS_CONNECTION_PASSWORD}@${process.env.REDIS_CONNECTION_HOST}:${process.env.REDIS_CONNECTION_PORT}`;
7
+
8
+ const redisClient = createClient({
9
+ url: redisConn
10
+ })
11
+ .on('error', err => logger.error('Redis Client ', { error: err }));
12
+
13
+ export const connect = async () => {
14
+ await redisClient.connect();
15
+ }
16
+
17
+ export const isConnected = () => {
18
+ return redisClient.isOpen;
19
+ }
20
+
21
+ export const disconnect = async () => {
22
+ await redisClient.disconnect();
23
+ }
24
+
25
+ const getKeyPrefix = (stateType: string) => `seeka:app:${process.env.SEEKA_APP_ID}:${stateType}`
26
+ const getKey = (stateType: string, key: string) => `${getKeyPrefix(stateType)}:${key}`
27
+
28
+ export async function getOrCreate<TState>(stateType: string, key: string, toCreate: TState): Promise<TState> {
29
+ const fullKey = getKey(stateType, key);
30
+ const existingStr = await redisClient.get(fullKey);
31
+ if (existingStr) return JSON.parse(existingStr);
32
+
33
+ await redisClient.set(fullKey, JSON.stringify(toCreate));
34
+
35
+ return toCreate;
36
+ }
37
+
38
+ export async function tryGet<TState>(stateType: string, key: string): Promise<TState | null> {
39
+ const fullKey = getKey(stateType, key);
40
+ const existingStr = await redisClient.get(fullKey);
41
+ if (existingStr) return JSON.parse(existingStr);
42
+
43
+ return null;
44
+ }
45
+
46
+ export async function getList<TState>(stateType: string): Promise<TState[]> {
47
+ const prefix = getKeyPrefix(stateType);
48
+ const allKeys = await redisClient.keys(`${prefix}:*`);
49
+ const listStr = await redisClient.mGet(allKeys);
50
+
51
+ if (listStr) return listStr.filter(e => Boolean(e)).map(e => JSON.parse(e as string));
52
+
53
+ return [];
54
+ }
55
+
56
+ export async function set<TState>(stateType: string, key: string, toCreate: TState): Promise<void> {
57
+ const fullKey = getKey(stateType, key);
58
+ await redisClient.set(fullKey, JSON.stringify(toCreate));
59
+ }
60
+
61
+ export async function remove(stateType: string, key: string): Promise<void> {
62
+ const fullKey = getKey(stateType, key);
63
+ await redisClient.del(fullKey);
64
+ }
@@ -0,0 +1,67 @@
1
+ import type { Logger } from 'winston';
2
+
3
+ import { getList, remove, set, tryGet } from '../redis';
4
+
5
+ export interface SeekaAppInstallState {
6
+ /** ID of the organisation that installed the app */
7
+ organisationId: string;
8
+ /** ID of the brand that installed the app */
9
+ organisationBrandId: string;
10
+ /** ID of the installation of the app */
11
+ applicationInstallId: string;
12
+ // Installation settings provided by the user installing the app
13
+ installationSettings: SampleAppInstallSettings
14
+ // State relating to the app and installation of the app
15
+ installationState: SampleAppInstallState;
16
+
17
+ // When the app was installed
18
+ installedAt: string; // new Date().toISOString()
19
+ }
20
+
21
+ export interface SampleAppInstallState {
22
+ stateItem1?: string
23
+ stateItem2?: string
24
+ grantedPermissions?: string[]
25
+ }
26
+
27
+ export type SampleAppInstallSettings = { [key: string]: any; } | {
28
+ myAppInstallSetting1: string | number | undefined;
29
+ myAppInstallSetting2: string | number | undefined;
30
+ }
31
+
32
+ const stateType = 'install'
33
+
34
+ export const tryGetInstallation = async (applicationInstallId: string, throwWhenNotFound: boolean, logger: Logger): Promise<SeekaAppInstallState | null> => {
35
+ const installation = await tryGet<SeekaAppInstallState>(stateType, applicationInstallId);
36
+ if (installation == null && throwWhenNotFound) {
37
+ throw new Error(`Seeka installation ${applicationInstallId} not found`);
38
+ }
39
+
40
+ return installation;
41
+ }
42
+
43
+ export const listInstallations = async (logger: Logger): Promise<SeekaAppInstallState[]> => {
44
+ const installations = await getList<SeekaAppInstallState>(stateType);
45
+
46
+ return installations;
47
+ }
48
+
49
+
50
+ export const createOrUpdateInstallation = async (state: SeekaAppInstallState, logger: Logger): Promise<SeekaAppInstallState> => {
51
+ if (!state.installationState) state.installationState = {};
52
+ if (!state.installedAt) state.installedAt = new Date().toISOString();
53
+
54
+ const creating = (await tryGetInstallation(state.applicationInstallId, false, logger)) === null;
55
+
56
+ await set(stateType, state.applicationInstallId, state);
57
+
58
+ logger.info(creating ? 'Created installation state' : 'Updated installation state', { applicationInstallId: state.applicationInstallId, organisationId: state.organisationId, organisationBrandId: state.organisationBrandId });
59
+
60
+ return state;
61
+ }
62
+
63
+ export const deleteInstallation = async (applicationInstallId: string, logger: Logger): Promise<void> => {
64
+ await remove(stateType, applicationInstallId);
65
+
66
+ logger.info('Deleted installation state', { applicationInstallId });
67
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "skipLibCheck": true,
5
+ "strict": false,
6
+ "esModuleInterop": true,
7
+ "outDir": "dist",
8
+ "rootDir": ".",
9
+ "sourceMap": true,
10
+ "module": "CommonJS",
11
+ "target": "ES6",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": false,
14
+ },
15
+ "include": [
16
+ "src/**/*.ts"
17
+ ],
18
+ "exclude": [
19
+ "node_modules",
20
+ "dist"
21
+ ]
22
+ }
@@ -0,0 +1,18 @@
1
+ SEEKA_APP_ID=
2
+ SEEKA_APP_SECRET=
3
+ SEEKA_DEBUG_ENABLED=true
4
+ SEEKA_INGEST_URL=
5
+ SEEKA_ISSUER_URL=
6
+ NODE_TLS_REJECT_UNAUTHORIZED=1
7
+ NETLIFY_ENABLED=false
8
+ NETLIFY_SITE_NAME=
9
+ REDIS_CONNECTION_USER=default
10
+ REDIS_CONNECTION_PASSWORD=
11
+ REDIS_CONNECTION_TLS=true
12
+ REDIS_CONNECTION_HOST=
13
+ REDIS_CONNECTION_PORT=
14
+ LOGGING_SEQ_SERVERURL=
15
+ LOGGING_SEQ_APIKEY=
16
+ LOGGING_LEVEL=silly
17
+ ENCRYPTION_SECRET=
18
+ INBOUND_HTTP_CALL_API_KEY=
@@ -0,0 +1,7 @@
1
+ /* eslint-env node */
2
+ module.exports = {
3
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
4
+ parser: '@typescript-eslint/parser',
5
+ plugins: ['@typescript-eslint'],
6
+ root: true,
7
+ };
@@ -0,0 +1,36 @@
1
+ # Local Netlify folder
2
+ .netlify
3
+ .env
4
+
5
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
6
+
7
+ # dependencies
8
+ /node_modules
9
+ /.pnp
10
+ .pnp.js
11
+ .yarn/install-state.gz
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # production
17
+ /build
18
+ /dist
19
+
20
+ # misc
21
+ .DS_Store
22
+ *.pem
23
+
24
+ # debug
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+
29
+ # local env files
30
+ .env*.local
31
+
32
+ # vercel
33
+ .vercel
34
+
35
+ # typescript
36
+ *.tsbuildinfo
@@ -0,0 +1,45 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "netlify dev",
6
+ "type": "node",
7
+ "request": "launch",
8
+ "skipFiles": [
9
+ "<node_internals>/**"
10
+ ],
11
+ "outFiles": [
12
+ "${workspaceFolder}/.netlify/functions-serve/**/*.js"
13
+ ],
14
+ "program": "${workspaceFolder}/node_modules/.bin/netlify",
15
+ "args": [
16
+ "dev"
17
+ ],
18
+ "console": "integratedTerminal",
19
+ "env": {
20
+ "BROWSER": "none"
21
+ },
22
+ "serverReadyAction": {
23
+ "pattern": "Server now ready on (https?://[\\w:.-]+)",
24
+ "uriFormat": "%s",
25
+ "action": "debugWithChrome"
26
+ }
27
+ },
28
+ {
29
+ "name": "netlify functions:serve",
30
+ "type": "node",
31
+ "request": "launch",
32
+ "skipFiles": [
33
+ "<node_internals>/**"
34
+ ],
35
+ "outFiles": [
36
+ "${workspaceFolder}/.netlify/functions-serve/**/*.js"
37
+ ],
38
+ "program": "${workspaceFolder}/node_modules/.bin/netlify",
39
+ "args": [
40
+ "functions:serve"
41
+ ],
42
+ "console": "integratedTerminal"
43
+ }
44
+ ]
45
+ }
@@ -0,0 +1,60 @@
1
+ # Seeka app - Netlify functions
2
+
3
+ ## Development
4
+
5
+ - `yarn install`
6
+ - `yarn dev`
7
+
8
+ ### Dependencies
9
+ - Python >=3.6.0 - https://www.python.org/downloads/ or `choco install python`
10
+
11
+ ### Invoking functions
12
+ Run `yarn dev:netlify:invoke:polling-example-job-scheduled` to trigger scheduled job which triggers multiple background jobs, 1 for each installation of the app
13
+
14
+ ### Debugging / local development before deployment
15
+ - This template uses [Netlify's live url debugging](https://answers.netlify.com/t/netlify-dev-with-support-for-custom-live-urls/101261)
16
+ - Supports VSCode debugging - See `.vscode/launch.json`. This is only supported on Linux. If using Windows then use WSL with an Ubuntu distro for support of attaching the VS code debugger
17
+
18
+ ## Logging
19
+ Centralised logging handled by [Winston](https://www.npmjs.com/package/winston). Winston is installed in this template and is also used in the Seeka SDK NPM packages.
20
+
21
+ [Seq](https://datalust.co/seq) Winston transport is installed in this template (optional).
22
+
23
+ To configure Seq options, see `_SEQ_` environment variables.
24
+
25
+ To install Seq on your development machine
26
+ 1. Run `docker run --name seq -d --restart=always -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:2024.2`
27
+ 2. Update your `LOGGING_SEQ_SERVERURL` environment variable to `http://localhost:5341` or `http://[YOUR MACHINE NAME].local:5341` if using WSL.
28
+
29
+ ## State management
30
+ Installations of your app and other state required for your app to function is stored in Redis. Another state provider can be swapped out for Redis, see `src/lib/state/seeka/installations.ts` for the file that manages the state of the installations.
31
+
32
+ ### Upstash (optional)
33
+ If you dont want to use Upstash for Redis then the connection strings in `.env` can be swapped with your Redis instance.
34
+
35
+ > If using Upstash then create a database before following the below guide to deploying your Netlify function.
36
+
37
+ > When choosing region - use US East (Ohio) - us-east-* as this is the same AWS region that the free Netlify functions run from. If not on the Netlify free plan, select the closest Upstash region that your Netlify function is hosted in to reduce latency between the function and the Redis database.
38
+
39
+ > Considerations - max connection limit (1000) of Upstash connections in relation to concurrent execution count of the Netlify functions
40
+
41
+ ## Deployment
42
+ This project comes ready to deploy for free to Netlify with a database and queues backed by Redis hosted by Upstash.
43
+
44
+ At the point of writing, Netlify and Upstash can be used to deploy your app for free.
45
+ - https://upstash.com/pricing
46
+ - https://www.netlify.com/pricing/
47
+
48
+ ### Deployment to Netlify
49
+ #### From your local machine
50
+ - `yarn deploy`
51
+
52
+ #### Using Netlify continuous delivery
53
+ See the [Netlify docs](https://docs.netlify.com/functions/deploy/?fn-language=ts)
54
+
55
+ ## Considerations when using this template
56
+ Each call to the netlify function will create a new connection to the database/Redis as calls to the Netlify function in quick succession will not share a single "warm" function host which is supported by Azure Functions and AWS Lambda.
57
+
58
+ ## References
59
+ - https://docs.netlify.com/cli/get-started/
60
+ - https://docs.netlify.com/functions/get-started/?fn-language=ts
@@ -0,0 +1,7 @@
1
+ [functions]
2
+ directory = "src/api"
3
+ node_bundler = "esbuild"
4
+
5
+ [dev]
6
+ command = "yarn dev"
7
+ port = 8888
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "seeka-app-example-name",
3
+ "version": "0.0.1",
4
+ "description": "Seeka example app for hosting on Netlify functions",
5
+ "author": "Seeka <platform@seeka.co>",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "private": true,
9
+ "engines": {
10
+ "node": ">=18.14.0"
11
+ },
12
+ "scripts": {
13
+ "dev:invoke:example-scheduled-job": "netlify functions:invoke --name polling-example-job-scheduled --port 8888 --identity",
14
+ "lint": "eslint",
15
+ "dev": "netlify dev --live=localdev --port 8888 --no-open",
16
+ "build": "netlify functions:build --functions=dist --src=src/api",
17
+ "deploy": "netlify deploy --build --skip-functions-cache --prod"
18
+ },
19
+ "dependencies": {
20
+ "@datalust/winston-seq": "^2.0.0",
21
+ "@netlify/functions": "^2.5.1",
22
+ "@seeka-labs/sdk-apps-server": "^1.0.1",
23
+ "axios": "^1.6.7",
24
+ "lodash-es": "^4.17.21",
25
+ "openid-client": "^5.6.4",
26
+ "redis": "^4.6.12",
27
+ "winston": "^3.11.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/lodash-es": "^4.17.12",
31
+ "@types/node": "^18",
32
+ "@typescript-eslint/eslint-plugin": "^6.19.1",
33
+ "@typescript-eslint/parser": "^6.19.1",
34
+ "eslint": "^8",
35
+ "netlify-cli": "^17.15.3",
36
+ "typescript": "^5.3.3"
37
+ }
38
+ }
@@ -0,0 +1,52 @@
1
+ import winston, { Logger } from 'winston';
2
+
3
+ import {
4
+ apiKeyHeaderName, BackgroundJobRequestContext, jobNames, signatureHeaderName, validateApiKey,
5
+ validateSignature
6
+ } from '@/lib/jobs';
7
+ import { backgroundJobLogger } from '@/lib/logging';
8
+ import { startServices, stopServices } from '@/lib/services';
9
+
10
+ import type { Config, Context } from "@netlify/functions"
11
+ export default async (req: Request, context: Context) => {
12
+ if (req.method != "POST") {
13
+ return new Response("Method not allowed", { status: 405 });
14
+ }
15
+
16
+ const bodyStr = await req.text();
17
+ const body = JSON.parse(bodyStr) as BackgroundJobRequestContext;
18
+ const logger = backgroundJobLogger(jobNames.exampleBackgroundJob, body, context);
19
+ logger.profile(`job.${jobNames.exampleBackgroundJob}`)
20
+
21
+ await startServices(logger);
22
+
23
+ if (process.env.SEEKA_DEBUG_ENABLED === 'true') {
24
+ logger.info('Received request to trigger background job', { body, headers: req.headers });
25
+ }
26
+ else {
27
+ logger.verbose('Received request to trigger background job', { body });
28
+ }
29
+
30
+ try {
31
+ // Verify request
32
+ validateApiKey(req.headers.get(apiKeyHeaderName) as string, logger);
33
+ validateSignature(req.headers.get(signatureHeaderName) as string, bodyStr, logger);
34
+
35
+ // Execute
36
+ await doWork(body, logger);
37
+
38
+ // Close db connections
39
+ await stopServices(logger);
40
+ }
41
+ catch (err) {
42
+ logger.error('Error executing background job', { ex: winston.exceptions.getAllInfo(err) })
43
+ }
44
+
45
+ logger.profile(`job.${jobNames.exampleBackgroundJob}`)
46
+
47
+ return new Response(undefined, { status: 202 })
48
+ }
49
+
50
+ const doWork = async (context: BackgroundJobRequestContext, logger: Logger) => {
51
+ logger.info('Ran background job')
52
+ }
@@ -0,0 +1,46 @@
1
+ import winston from 'winston';
2
+
3
+ import { jobNames, triggerBackgroundJob } from '@/lib/jobs';
4
+ import { backgroundJobLogger } from '@/lib/logging';
5
+ import { startServices, stopServices } from '@/lib/services';
6
+ import { listInstallations } from '@/lib/state/seeka/installations';
7
+
8
+ import type { Config, Context } from "@netlify/functions"
9
+ export const config: Config = {
10
+ schedule: "@hourly"
11
+ }
12
+
13
+ export default async (req: Request, context: Context) => {
14
+ const { next_run } = (await req.json()) as { next_run: string }
15
+
16
+ const logger = backgroundJobLogger(jobNames.examplePollingScheduledJob, undefined, context);
17
+
18
+ logger.profile(`job.${jobNames.examplePollingScheduledJob}`)
19
+ if (process.env.SEEKA_DEBUG_ENABLED === 'true') {
20
+ logger.info('Received request to trigger scheduled job', { headers: req.headers, next_run });
21
+ }
22
+ else {
23
+ logger.verbose('Received request to trigger scheduled job', { next_run });
24
+ }
25
+
26
+ await startServices(logger);
27
+
28
+ const allInstallations = await listInstallations(logger);
29
+ logger.verbose(`Triggering background job for ${allInstallations.length} installations`, { allInstallations })
30
+
31
+ // Close db connections
32
+ await stopServices(logger);
33
+
34
+ const promises = allInstallations.map(installation => triggerBackgroundJob(jobNames.exampleBackgroundJob, installation, logger))
35
+
36
+ try {
37
+ await Promise.all(promises);
38
+ }
39
+ catch (err) {
40
+ logger.error('Error triggering background jobs', { ex: winston.exceptions.getAllInfo(err) })
41
+ }
42
+
43
+ logger.profile(`job.${jobNames.examplePollingScheduledJob}`)
44
+
45
+ return new Response(undefined, { status: 202 })
46
+ }