wip: background tasks

This commit is contained in:
Mythie
2024-05-15 18:55:05 +10:00
parent 1647f7c4a0
commit 108054a133
23 changed files with 752 additions and 29 deletions

View File

@ -35,9 +35,9 @@
"next-plausible": "^3.10.1",
"perfect-freehand": "^1.2.0",
"posthog-js": "^1.77.3",
"react": "18.2.0",
"react": "18.3.1",
"react-confetti": "^6.1.0",
"react-dom": "18.2.0",
"react-dom": "18.3.1",
"react-hook-form": "^7.43.9",
"react-icons": "^4.11.0",
"recharts": "^2.7.2",
@ -58,4 +58,4 @@
"next": "$next"
}
}
}
}

View File

@ -41,8 +41,8 @@
"perfect-freehand": "^1.2.0",
"posthog-js": "^1.75.3",
"posthog-node": "^3.1.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.43.9",
"react-hotkeys-hook": "^4.4.1",
@ -75,4 +75,4 @@
"next": "$next"
}
}
}
}

View File

@ -0,0 +1,11 @@
import { jobsClient } from '@documenso/lib/jobs/client';
import '@documenso/lib/jobs/definitions';
export const config = {
maxDuration: 300,
api: {
bodyParser: false,
},
};
export default jobsClient.getApiHandler();

View File

@ -4,6 +4,13 @@ services:
database:
image: postgres:15
container_name: database
volumes:
- documenso_database:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
interval: 10s
timeout: 5s
retries: 5
environment:
- POSTGRES_USER=documenso
- POSTGRES_PASSWORD=password
@ -33,5 +40,43 @@ services:
entrypoint: sh
command: -c 'mkdir -p /data/documenso && minio server /data --console-address ":9001" --address ":9002"'
triggerdotdev:
image: ghcr.io/triggerdotdev/trigger.dev:latest
container_name: triggerdotdev
environment:
- LOGIN_ORIGIN=http://localhost:3030
- APP_ORIGIN=http://localhost:3030
- PORT=3030
- REMIX_APP_PORT=3030
- MAGIC_LINK_SECRET=secret
- SESSION_SECRET=secret
- ENCRYPTION_KEY=deadbeefcafefeed
- DATABASE_URL=postgresql://trigger:password@triggerdotdev_database:5432/trigger
- DIRECT_URL=postgresql://trigger:password@triggerdotdev_database:5432/trigger
- RUNTIME_PLATFORM=docker-compose
ports:
- 3030:3030
depends_on:
- triggerdotdev_database
triggerdotdev_database:
container_name: triggerdotdev_database
image: postgres:15
volumes:
- triggerdotdev_database:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
interval: 10s
timeout: 5s
retries: 5
environment:
- POSTGRES_USER=trigger
- POSTGRES_PASSWORD=password
- POSTGRES_DB=trigger
ports:
- 54321:5432
volumes:
minio:
documenso_database:
triggerdotdev_database:

318
package-lock.json generated
View File

@ -59,9 +59,9 @@
"next-plausible": "^3.10.1",
"perfect-freehand": "^1.2.0",
"posthog-js": "^1.77.3",
"react": "18.2.0",
"react": "18.3.1",
"react-confetti": "^6.1.0",
"react-dom": "18.2.0",
"react-dom": "18.3.1",
"react-hook-form": "^7.43.9",
"react-icons": "^4.11.0",
"recharts": "^2.7.2",
@ -81,6 +81,37 @@
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==",
"dev": true
},
"apps/marketing/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"apps/marketing/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"apps/marketing/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"apps/marketing/node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@ -125,8 +156,8 @@
"perfect-freehand": "^1.2.0",
"posthog-js": "^1.75.3",
"posthog-node": "^3.1.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.43.9",
"react-hotkeys-hook": "^4.4.1",
@ -158,6 +189,37 @@
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==",
"dev": true
},
"apps/web/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"apps/web/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"apps/web/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"apps/web/node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@ -8343,6 +8405,106 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@trigger.dev/core": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/core/-/core-2.3.18.tgz",
"integrity": "sha512-j2EdCeyMkZ+zlVnnHl5zmBb+YURSw4x75NqQU1G5X08pQAza7G0qEn8DDGIMR5ieUMiHP0WS9oYy/voYdNfibQ==",
"dependencies": {
"ulidx": "^2.2.1",
"zod": "3.22.3",
"zod-error": "1.5.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@trigger.dev/core-backend": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/core-backend/-/core-backend-2.3.18.tgz",
"integrity": "sha512-LVeeerraGeqKNd2gtajQY+mnGWqkYW7Q2r5oWpL5xIZ8aQg3HRhSIfZs1dryexwKlfqnRjGWueGTy2+j1tbzcg==",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@trigger.dev/core/node_modules/zod": {
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@trigger.dev/nextjs": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/nextjs/-/nextjs-2.3.18.tgz",
"integrity": "sha512-ZS0RTZNrzGEKfOLQLYt3iqlNquD7pd39Hpd/+2tvRCaPSQ3qPYQvdjBSueW0OURZSQSiNno5VUYR5vbVBcAaXA==",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@trigger.dev/sdk": "^2.3.18",
"next": ">=12.0.0"
}
},
"node_modules/@trigger.dev/sdk": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/sdk/-/sdk-2.3.18.tgz",
"integrity": "sha512-Bjxgl4BbWOAL8rhxeBkl7SzvLLRBMJjiftq/7W7u96MDyPRFUoZZvVMSZzTJufnLBf/xS2JTi8LWU8gzhDJDvw==",
"dependencies": {
"@trigger.dev/core": "^2.3.18",
"@trigger.dev/core-backend": "^2.3.18",
"chalk": "^5.2.0",
"cronstrue": "^2.21.0",
"debug": "^4.3.4",
"evt": "^2.4.13",
"get-caller-file": "^2.0.5",
"git-remote-origin-url": "^4.0.0",
"git-repo-info": "^2.1.1",
"slug": "^6.0.0",
"terminal-link": "^3.0.0",
"ulid": "^2.3.0",
"uuid": "^9.0.0",
"ws": "^8.11.0",
"zod": "3.22.3"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@trigger.dev/sdk/node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@trigger.dev/sdk/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@trigger.dev/sdk/node_modules/zod": {
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz",
@ -11109,6 +11271,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"node_modules/cronstrue": {
"version": "2.50.0",
"resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.50.0.tgz",
"integrity": "sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==",
"bin": {
"cronstrue": "bin/cli.js"
}
},
"node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
@ -13321,6 +13491,16 @@
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true
},
"node_modules/evt": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/evt/-/evt-2.5.7.tgz",
"integrity": "sha512-dr7Wd16ry5F8WNU1xXLKpFpO3HsoAGg8zC48e08vDdzMzGWCP9/QFGt1PQptEEDh8SwYP3EL8M+d/Gb0kgUp6g==",
"dependencies": {
"minimal-polyfills": "^2.2.3",
"run-exclusive": "^2.2.19",
"tsafe": "^1.6.6"
}
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@ -14072,6 +14252,36 @@
"node": ">=10"
}
},
"node_modules/git-remote-origin-url": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-4.0.0.tgz",
"integrity": "sha512-EAxDksNdjuWgmVW9pVvA9jQDi/dmTaiDONktIy7qiRRhBZUI4FQK1YvBvteuTSX24aNKg9lfgxNYJEeeSXe6DA==",
"dependencies": {
"gitconfiglocal": "^2.1.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/git-repo-info": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-2.1.1.tgz",
"integrity": "sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==",
"engines": {
"node": ">= 4.0"
}
},
"node_modules/gitconfiglocal": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz",
"integrity": "sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==",
"dependencies": {
"ini": "^1.3.2"
}
},
"node_modules/glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@ -15828,6 +16038,11 @@
"node": ">=0.10"
}
},
"node_modules/layerr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/layerr/-/layerr-2.1.0.tgz",
"integrity": "sha512-xDD9suWxfBYeXgqffRVH/Wqh+mqZrQcqPRn0I0ijl7iJQ7vu8gMGPt1Qop59pEW/jaIDNUN7+PX1Qk40+vuflg=="
},
"node_modules/lazy-ass": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
@ -17627,6 +17842,11 @@
"node": ">=6"
}
},
"node_modules/minimal-polyfills": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/minimal-polyfills/-/minimal-polyfills-2.2.3.tgz",
"integrity": "sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw=="
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -21645,6 +21865,14 @@
"fsevents": "~2.3.2"
}
},
"node_modules/run-exclusive": {
"version": "2.2.19",
"resolved": "https://registry.npmjs.org/run-exclusive/-/run-exclusive-2.2.19.tgz",
"integrity": "sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA==",
"dependencies": {
"minimal-polyfills": "^2.2.3"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@ -22063,6 +22291,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/slug": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/slug/-/slug-6.1.0.tgz",
"integrity": "sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA=="
},
"node_modules/sort-object-keys": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz",
@ -22675,7 +22908,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
"integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0",
"supports-color": "^7.0.0"
@ -23015,7 +23247,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
"integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==",
"dev": true,
"dependencies": {
"ansi-escapes": "^4.2.1",
"supports-hyperlinks": "^2.0.0"
@ -23031,7 +23262,6 @@
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.21.3"
},
@ -23046,7 +23276,6 @@
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true,
"engines": {
"node": ">=10"
},
@ -23357,6 +23586,11 @@
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w=="
},
"node_modules/tsafe": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/tsafe/-/tsafe-1.6.6.tgz",
"integrity": "sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g=="
},
"node_modules/tsconfig-paths": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
@ -24301,6 +24535,25 @@
"integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
"dev": true
},
"node_modules/ulid": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz",
"integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==",
"bin": {
"ulid": "bin/cli.js"
}
},
"node_modules/ulidx": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ulidx/-/ulidx-2.3.0.tgz",
"integrity": "sha512-36piWNqcdp9hKlQewyeehCaALy4lyx3FodsCxHuV6i0YdexSkjDOubwxEVr2yi4kh62L/0MgyrxqG4K+qtovnw==",
"dependencies": {
"layerr": "^2.0.1"
},
"engines": {
"node": ">=16"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -25695,6 +25948,14 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-error": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/zod-error/-/zod-error-1.5.0.tgz",
"integrity": "sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==",
"dependencies": {
"zod": "^3.20.2"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
@ -25970,11 +26231,22 @@
"micro": "^10.0.1",
"next": "14.0.3",
"next-auth": "4.24.5",
"react": "18.2.0",
"react": "18.3.1",
"ts-pattern": "^5.0.5",
"zod": "^3.22.4"
}
},
"packages/ee/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"packages/email": {
"name": "@documenso/email",
"version": "1.0.0",
@ -27218,6 +27490,8 @@
"@pdf-lib/fontkit": "^1.1.1",
"@scure/base": "^1.1.3",
"@sindresorhus/slugify": "^2.2.1",
"@trigger.dev/nextjs": "^2.3.18",
"@trigger.dev/sdk": "^2.3.18",
"@upstash/redis": "^1.20.6",
"@vvo/tzdb": "^6.117.0",
"kysely": "^0.26.3",
@ -27229,7 +27503,7 @@
"pdf-lib": "^1.17.1",
"pg": "^8.11.3",
"playwright": "1.43.0",
"react": "18.2.0",
"react": "18.3.1",
"remeda": "^1.27.1",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
@ -27277,6 +27551,17 @@
"node": "^14 || ^16 || >=18"
}
},
"packages/lib/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"packages/prettier-config": {
"name": "@documenso/prettier-config",
"version": "0.0.0",
@ -27679,10 +27964,21 @@
"@types/luxon": "^3.3.2",
"@types/react": "18.2.18",
"@types/react-dom": "18.2.7",
"react": "18.2.0",
"react": "18.3.1",
"typescript": "5.2.2"
}
},
"packages/ui/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"packages/ui/node_modules/react-pdf": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.3.tgz",

View File

@ -19,8 +19,8 @@
"micro": "^10.0.1",
"next": "14.0.3",
"next-auth": "4.24.5",
"react": "18.2.0",
"react": "18.3.1",
"ts-pattern": "^5.0.5",
"zod": "^3.22.4"
}
}
}

View File

@ -0,0 +1,3 @@
import { JobClient } from './client/client';
export const jobsClient = JobClient.getInstance();

View File

@ -0,0 +1,41 @@
import { z } from 'zod';
export const ZTriggerJobOptionsSchema = z.object({
id: z.string().optional(),
name: z.string(),
payload: z.unknown().refine((x) => x !== undefined, { message: 'payload is required' }),
timestamp: z.number().optional(),
});
// The Omit is a temporary workaround for a "bug" in the zod library
// @see: https://github.com/colinhacks/zod/issues/2966
export type TriggerJobOptions = Omit<z.infer<typeof ZTriggerJobOptionsSchema>, 'payload'> & {
payload: unknown;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type JobDefinition<T = any> = {
id: string;
name: string;
version: string;
enabled?: boolean;
trigger: {
name: string;
schema?: z.ZodSchema<T>;
};
handler: (options: { payload: T; io: JobRunIO }) => Promise<Json | void>;
};
export interface JobRunIO {
// stableRun<T extends Json | void>(cacheKey: string, callback: (io: JobRunIO) => T | Promise<T>): Promise<T>;
stableRun<T extends Json | void>(cacheKey: string, callback: () => Promise<T>): Promise<T>;
triggerJob(cacheKey: string, options: TriggerJobOptions): Promise<unknown>;
wait(cacheKey: string, ms: number): Promise<void>;
logger: {
info(...args: unknown[]): void;
error(...args: unknown[]): void;
debug(...args: unknown[]): void;
warn(...args: unknown[]): void;
log(...args: unknown[]): void;
};
}

View File

@ -0,0 +1,14 @@
/**
* Below type is borrowed from Trigger.dev's SDK, it may be moved elsewhere later.
*/
type JsonPrimitive = string | number | boolean | null | undefined | Date | symbol;
type JsonArray = Json[];
type JsonRecord<T> = {
[Property in keyof T]: Json;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Json<T = any> = JsonPrimitive | JsonArray | JsonRecord<T>;

View File

@ -0,0 +1,19 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { JobDefinition, TriggerJobOptions } from './_internal/job';
export abstract class BaseJobProvider {
// eslint-disable-next-line @typescript-eslint/require-await
public async triggerJob(_options: TriggerJobOptions): Promise<void> {
throw new Error('Not implemented');
}
// eslint-disable-next-line @typescript-eslint/require-await
public defineJob<T>(_job: JobDefinition<T>): void {
throw new Error('Not implemented');
}
public getApiHandler(): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {
throw new Error('Not implemented');
}
}

View File

@ -0,0 +1,38 @@
import type { JobDefinition, TriggerJobOptions } from './_internal/job';
import type { BaseJobProvider as JobClientProvider } from './base';
import { LocalJobProvider } from './local';
import { TriggerJobProvider } from './trigger';
export class JobClient {
private static _instance: JobClient;
private _provider: JobClientProvider;
private constructor() {
if (process.env.NEXT_PRIVATE_JOBS_PROVIDER === 'trigger') {
this._provider = TriggerJobProvider.getInstance();
}
this._provider = LocalJobProvider.getInstance();
}
public static getInstance() {
if (!this._instance) {
this._instance = new JobClient();
}
return this._instance;
}
public async triggerJob(options: TriggerJobOptions) {
return this._provider.triggerJob(options);
}
public defineJob<T>(job: JobDefinition<T>) {
return this._provider.defineJob(job);
}
public getApiHandler() {
return this._provider.getApiHandler();
}
}

View File

@ -0,0 +1,124 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { json } from 'micro';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { sign } from '../../server-only/crypto/sign';
import { verify } from '../../server-only/crypto/verify';
import type { JobDefinition, JobRunIO, TriggerJobOptions } from './_internal/job';
import { BaseJobProvider } from './base';
export class LocalJobProvider extends BaseJobProvider {
private static _instance: LocalJobProvider;
private _jobDefinitions: Record<string, JobDefinition> = {};
private constructor() {
super();
}
static getInstance() {
if (!this._instance) {
this._instance = new LocalJobProvider();
}
return this._instance;
}
public defineJob<T>(definition: JobDefinition<T>) {
this._jobDefinitions[definition.id] = {
...definition,
enabled: definition.enabled ?? true,
};
}
public async triggerJob(options: TriggerJobOptions) {
const signature = sign(options);
await Promise.race([
fetch(`${NEXT_PUBLIC_WEBAPP_URL()}/api/jobs/trigger`, {
method: 'POST',
body: JSON.stringify(options),
headers: {
'Content-Type': 'application/json',
'X-Job-Signature': signature,
},
}),
new Promise((resolve) => {
setTimeout(resolve, 150);
}),
]);
}
public getApiHandler() {
return async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
const signature = req.headers['x-job-signature'];
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const options = (await json(req)) as TriggerJobOptions;
const definition = this._jobDefinitions[options.name];
if (typeof signature !== 'string' || typeof options !== 'object') {
res.status(400).send('Bad request');
return;
}
if (!definition) {
res.status(404).send('Job not found');
return;
}
if (definition && !definition.enabled) {
console.log('Attempted to trigger a disabled job', options.name);
res.status(404).send('Job not found');
return;
}
if (!signature || !verify(options, signature)) {
res.status(401).send('Unauthorized');
return;
}
if (definition.trigger.schema) {
const result = definition.trigger.schema.safeParse(options.payload);
if (!result.success) {
res.status(400).send('Bad request');
return;
}
}
console.log(`[JOBS]: Triggering job ${options.name} with payload`, options.payload);
await definition.handler({
payload: options.payload,
io: this.createJobRunIO(options.name),
});
res.status(200).send('OK');
} else {
res.status(405).send('Method not allowed');
}
};
}
private createJobRunIO(jobId: string): JobRunIO {
return {
stableRun: async (_cacheKey, callback) => await callback(),
triggerJob: async (_cacheKey, payload) => await this.triggerJob(payload),
logger: {
debug: (...args) => console.debug(`[${jobId}]`, ...args),
error: (...args) => console.error(`[${jobId}]`, ...args),
info: (...args) => console.info(`[${jobId}]`, ...args),
log: (...args) => console.log(`[${jobId}]`, ...args),
warn: (...args) => console.warn(`[${jobId}]`, ...args),
},
// eslint-disable-next-line @typescript-eslint/require-await
wait: async () => {
throw new Error('Not implemented');
},
};
}
}

View File

@ -0,0 +1,75 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { createPagesRoute } from '@trigger.dev/nextjs';
import type { IO } from '@trigger.dev/sdk';
import { TriggerClient, eventTrigger } from '@trigger.dev/sdk';
import type { JobDefinition, JobRunIO, TriggerJobOptions } from './_internal/job';
import { BaseJobProvider } from './base';
export class TriggerJobProvider extends BaseJobProvider {
private static _instance: TriggerJobProvider;
private _client: TriggerClient;
private constructor(options: { client: TriggerClient }) {
super();
this._client = options.client;
}
static getInstance() {
if (!this._instance) {
const client = new TriggerClient({
id: 'documenso-app',
apiKey: process.env.NEXT_PRIVATE_TRIGGER_API_KEY,
apiUrl: process.env.NEXT_PRIVATE_TRIGGER_API_URL,
});
this._instance = new TriggerJobProvider({ client });
}
return this._instance;
}
public defineJob<T>(job: JobDefinition<T>): void {
this._client.defineJob({
id: job.id,
name: job.name,
version: job.version,
trigger: eventTrigger({
name: job.trigger.name,
schema: job.trigger.schema,
}),
run: async (payload, io) => job.handler({ payload, io: this.convertTriggerIoToJobRunIo(io) }),
});
}
public async triggerJob(_options: TriggerJobOptions): Promise<void> {
await this._client.sendEvent({
id: _options.id,
name: _options.name,
payload: _options.payload,
timestamp: _options.timestamp ? new Date(_options.timestamp) : undefined,
});
}
public getApiHandler(): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {
const { handler } = createPagesRoute(this._client);
return handler;
}
private convertTriggerIoToJobRunIo(io: IO) {
return {
wait: io.wait,
logger: io.logger,
stableRun: async (cacheKey, callback) => io.runTask(cacheKey, callback),
triggerJob: async (cacheKey, payload) =>
io.sendEvent(cacheKey, {
...payload,
timestamp: payload.timestamp ? new Date(payload.timestamp) : undefined,
}),
} satisfies JobRunIO;
}
}

View File

@ -0,0 +1 @@
export * from './send-confirmation-email';

View File

@ -0,0 +1,23 @@
import { z } from 'zod';
import { sendConfirmationToken } from '../../server-only/user/send-confirmation-token';
import { jobsClient } from '../client';
jobsClient.defineJob({
id: 'send.confirmation.email',
name: 'Send Confirmation Email',
version: '1-0-0',
trigger: {
name: 'send.confirmation.email',
schema: z.object({
email: z.string().email(),
force: z.boolean().optional(),
}),
},
handler: async ({ payload }) => {
await sendConfirmationToken({
email: payload.email,
force: payload.force,
});
},
});

View File

@ -14,11 +14,11 @@ import { prisma } from '@documenso/prisma';
import { IdentityProvider, UserSecurityAuditLogType } from '@documenso/prisma/client';
import { AppError, AppErrorCode } from '../errors/app-error';
import { jobsClient } from '../jobs/client';
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa';
import { getMostRecentVerificationTokenByUserId } from '../server-only/user/get-most-recent-verification-token-by-user-id';
import { getUserByEmail } from '../server-only/user/get-user-by-email';
import { sendConfirmationToken } from '../server-only/user/send-confirmation-token';
import type { TAuthenticationResponseJSONSchema } from '../types/webauthn';
import { ZAuthenticationResponseJSONSchema } from '../types/webauthn';
import { extractNextAuthRequestMetadata } from '../universal/extract-request-metadata';
@ -108,7 +108,12 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
mostRecentToken.expires.valueOf() <= Date.now() ||
DateTime.fromJSDate(mostRecentToken.createdAt).diffNow('minutes').minutes > -5
) {
await sendConfirmationToken({ email });
await jobsClient.triggerJob({
name: 'send.confirmation.email',
payload: {
email: user.email,
},
});
}
throw new Error(ErrorCode.UNVERIFIED_EMAIL);

View File

@ -32,6 +32,8 @@
"@pdf-lib/fontkit": "^1.1.1",
"@scure/base": "^1.1.3",
"@sindresorhus/slugify": "^2.2.1",
"@trigger.dev/nextjs": "^2.3.18",
"@trigger.dev/sdk": "^2.3.18",
"@upstash/redis": "^1.20.6",
"@vvo/tzdb": "^6.117.0",
"kysely": "^0.26.3",
@ -43,7 +45,7 @@
"pdf-lib": "^1.17.1",
"pg": "^8.11.3",
"playwright": "1.43.0",
"react": "18.2.0",
"react": "18.3.1",
"remeda": "^1.27.1",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",

View File

@ -2,7 +2,7 @@ import { DateTime } from 'luxon';
import { prisma } from '@documenso/prisma';
import { sendConfirmationToken } from './send-confirmation-token';
import { jobsClient } from '../../jobs/client';
export type VerifyEmailProps = {
token: string;
@ -40,7 +40,12 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
!mostRecentToken ||
DateTime.now().minus({ hours: 1 }).toJSDate() > mostRecentToken.createdAt
) {
await sendConfirmationToken({ email: verificationToken.user.email });
await jobsClient.triggerJob({
name: 'send.confirmation.email',
payload: {
email: verificationToken.user.email,
},
});
}
return valid;

View File

@ -5,6 +5,7 @@ import { env } from 'next-runtime-env';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { jobsClient } from '@documenso/lib/jobs/client';
import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';
import { createPasskeyAuthenticationOptions } from '@documenso/lib/server-only/auth/create-passkey-authentication-options';
@ -15,7 +16,6 @@ import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
import { compareSync } from '@documenso/lib/server-only/auth/hash';
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
import { createUser } from '@documenso/lib/server-only/user/create-user';
import { sendConfirmationToken } from '@documenso/lib/server-only/user/send-confirmation-token';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure, procedure, router } from '../trpc';
@ -52,7 +52,12 @@ export const authRouter = router({
const user = await createUser({ name, email, password, signature, url });
await sendConfirmationToken({ email: user.email });
await jobsClient.triggerJob({
name: 'send.confirmation.email',
payload: {
email: user.email,
},
});
return user;
} catch (err) {

View File

@ -2,13 +2,13 @@ import { TRPCError } from '@trpc/server';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { jobsClient } from '@documenso/lib/jobs/client';
import { getSubscriptionsByUserId } from '@documenso/lib/server-only/subscription/get-subscriptions-by-user-id';
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
import { sendConfirmationToken } from '@documenso/lib/server-only/user/send-confirmation-token';
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
import { updatePublicProfile } from '@documenso/lib/server-only/user/update-public-profile';
@ -200,7 +200,12 @@ export const profileRouter = router({
try {
const { email } = input;
return await sendConfirmationToken({ email });
await jobsClient.triggerJob({
name: 'send.confirmation.email',
payload: {
email,
},
});
} catch (err) {
console.error(err);

View File

@ -68,6 +68,14 @@ declare namespace NodeJS {
//
NEXT_PRIVATE_BROWSERLESS_URL?: string;
NEXT_PRIVATE_JOBS_PROVIDER?: 'trigger' | 'local';
/**
* Trigger.dev environment variables
*/
NEXT_PRIVATE_TRIGGER_API_KEY?: string;
NEXT_PRIVATE_TRIGGER_API_URL?: string;
/**
* Vercel environment variables
*/

View File

@ -22,7 +22,7 @@
"@types/luxon": "^3.3.2",
"@types/react": "18.2.18",
"@types/react-dom": "18.2.7",
"react": "18.2.0",
"react": "18.3.1",
"typescript": "5.2.2"
},
"dependencies": {

View File

@ -108,6 +108,9 @@
"NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET",
"NEXT_PRIVATE_GITHUB_TOKEN",
"NEXT_PRIVATE_BROWSERLESS_URL",
"NEXT_PRIVATE_JOBS_PROVIDER",
"NEXT_PRIVATE_TRIGGER_API_KEY",
"NEXT_PRIVATE_TRIGGER_API_URL",
"CI",
"VERCEL",
"VERCEL_ENV",