diff --git a/apps/web/package.json b/apps/web/package.json index fd4faa0c1..11a6977ca 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,7 @@ "copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs" }, "dependencies": { + "@documenso/api": "*", "@documenso/assets": "*", "@documenso/ee": "*", "@documenso/lib": "*", diff --git a/apps/web/src/app/(dashboard)/settings/tokens/page.tsx b/apps/web/src/app/(dashboard)/settings/tokens/page.tsx new file mode 100644 index 000000000..8951098c4 --- /dev/null +++ b/apps/web/src/app/(dashboard)/settings/tokens/page.tsx @@ -0,0 +1,74 @@ +import { DateTime } from 'luxon'; + +import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; +import { getUserTokens } from '@documenso/lib/server-only/public-api/get-all-user-tokens'; +import { Button } from '@documenso/ui/primitives/button'; + +import DeleteTokenDialog from '~/components/(dashboard)/settings/token/delete-token-dialog'; +import { LocaleDate } from '~/components/formatter/locale-date'; +import { ApiTokenForm } from '~/components/forms/token'; + +export default async function ApiTokensPage() { + const { user } = await getRequiredServerComponentSession(); + + const tokens = await getUserTokens({ userId: user.id }); + + return ( +
+

API Tokens

+ +

+ On this page, you can create new API tokens and manage the existing ones. +

+ +
+ + + +
+ +

Your existing tokens

+ + {tokens.length === 0 && ( +
+

+ Your tokens will be shown here once you create them. +

+
+ )} + + {tokens.length > 0 && ( +
+ {tokens.map((token) => ( +
+
+
+
{token.name}
+ +

+ Created on +

+ {token.expires ? ( +

+ Expires on +

+ ) : ( +

+ Token doesn't have an expiration date +

+ )} +
+ +
+ + + +
+
+
+ ))} +
+ )} +
+ ); +} diff --git a/apps/web/src/app/api/v1/openapi/page.tsx b/apps/web/src/app/api/v1/openapi/page.tsx new file mode 100644 index 000000000..ca5c3a5ed --- /dev/null +++ b/apps/web/src/app/api/v1/openapi/page.tsx @@ -0,0 +1,3 @@ +'use client'; + +export { OpenApiDocsPage as default } from '@documenso/api/v1/api-documentation'; diff --git a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx index a767b9700..6b9ec7fdf 100644 --- a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx +++ b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { + Braces, CreditCard, FileSpreadsheet, Lock, @@ -98,6 +99,13 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => { + + + + API Tokens + + + {isBillingEnabled && ( diff --git a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx index 572c91c76..e87c47b67 100644 --- a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx +++ b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx @@ -5,7 +5,7 @@ import type { HTMLAttributes } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { CreditCard, Lock, User, Users } from 'lucide-react'; +import { Braces, CreditCard, Lock, User, Users } from 'lucide-react'; import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag'; import { cn } from '@documenso/ui/lib/utils'; @@ -64,6 +64,19 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => { + + + + {isBillingEnabled && ( + + + + {isBillingEnabled && ( + )} + + + + + Are you sure you want to delete this token? + + + Please note that this action is irreversible. Once confirmed, your token will be + permanently deleted. + + + +
+ +
+ ( + + + Confirm by typing:{' '} + + {deleteMessage} + + + + + + + + + )} + /> + + +
+ + + +
+
+
+
+ +
+ + ); +} diff --git a/apps/web/src/components/forms/token.tsx b/apps/web/src/components/forms/token.tsx new file mode 100644 index 000000000..fa5ae17a0 --- /dev/null +++ b/apps/web/src/components/forms/token.tsx @@ -0,0 +1,255 @@ +'use client'; + +import { useState } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard'; +import { TRPCClientError } from '@documenso/trpc/client'; +import { trpc } from '@documenso/trpc/react'; +import type { TCreateTokenMutationSchema } from '@documenso/trpc/server/api-token-router/schema'; +import { ZCreateTokenMutationSchema } from '@documenso/trpc/server/api-token-router/schema'; +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; +import { Card, CardContent } from '@documenso/ui/primitives/card'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; +import { Input } from '@documenso/ui/primitives/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@documenso/ui/primitives/select'; +import { Switch } from '@documenso/ui/primitives/switch'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +import { EXPIRATION_DATES } from '../(dashboard)/settings/token/contants'; + +const ZCreateTokenFormSchema = ZCreateTokenMutationSchema.extend({ + enabled: z.boolean(), +}); + +type TCreateTokenFormSchema = z.infer; + +export type ApiTokenFormProps = { + className?: string; +}; + +export const ApiTokenForm = ({ className }: ApiTokenFormProps) => { + const router = useRouter(); + + const [, copy] = useCopyToClipboard(); + const { toast } = useToast(); + + const [newlyCreatedToken, setNewlyCreatedToken] = useState(''); + const [noExpirationDate, setNoExpirationDate] = useState(false); + + const { mutateAsync: createTokenMutation } = trpc.apiToken.createToken.useMutation({ + onSuccess(data) { + setNewlyCreatedToken(data.token); + }, + }); + + const form = useForm({ + resolver: zodResolver(ZCreateTokenFormSchema), + defaultValues: { + tokenName: '', + expirationDate: '', + enabled: false, + }, + }); + + const copyToken = async (token: string) => { + try { + const copied = await copy(token); + + if (!copied) { + throw new Error('Unable to copy the token'); + } + + toast({ + title: 'Token copied to clipboard', + description: 'The token was copied to your clipboard.', + }); + } catch (error) { + toast({ + title: 'Unable to copy token', + description: 'We were unable to copy the token to your clipboard. Please try again.', + variant: 'destructive', + }); + } + }; + + const onSubmit = async ({ tokenName, expirationDate }: TCreateTokenMutationSchema) => { + try { + await createTokenMutation({ + tokenName, + expirationDate: noExpirationDate ? null : expirationDate, + }); + + toast({ + title: 'Token created', + description: 'A new token was created successfully.', + duration: 5000, + }); + + form.reset(); + + router.refresh(); + } catch (error) { + if (error instanceof TRPCClientError && error.data?.code === 'BAD_REQUEST') { + toast({ + title: 'An error occurred', + description: error.message, + variant: 'destructive', + }); + } else { + toast({ + title: 'An unknown error occurred', + variant: 'destructive', + duration: 5000, + description: + 'We encountered an unknown error while attempting create the new token. Please try again later.', + }); + } + } + }; + + return ( +
+
+ +
+ ( + + Token name + +
+ + + +
+ + + Please enter a meaningful name for your token. This will help you identify it + later. + + + +
+ )} + /> + +
+ ( + + Token expiration date + +
+ + + +
+ + +
+ )} + /> + + ( + + Never expire + +
+ { + setNoExpirationDate((prev) => !prev); + field.onChange(val); + }} + /> +
+
+ +
+ )} + /> +
+ + + +
+ +
+
+
+ + + {newlyCreatedToken && ( + + +

+ Your token was created successfully! Make sure to copy it because you won't be able to + see it again! +

+ +

+ {newlyCreatedToken} +

+ + +
+
+ )} +
+ ); +}; diff --git a/apps/web/src/pages/api/v1/[...ts-rest].tsx b/apps/web/src/pages/api/v1/[...ts-rest].tsx new file mode 100644 index 000000000..fcc5e4ffe --- /dev/null +++ b/apps/web/src/pages/api/v1/[...ts-rest].tsx @@ -0,0 +1,17 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +import { createNextRouter } from '@documenso/api/next'; +import { ApiContractV1 } from '@documenso/api/v1/contract'; +import { ApiContractV1Implementation } from '@documenso/api/v1/implementation'; + +const nextRouteHandler = createNextRouter(ApiContractV1, ApiContractV1Implementation, { + responseValidation: true, +}); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // TODO: Dirty hack to make ts-rest handler work with next.js in a more intuitive way. + req.query['ts-rest'] = Array.isArray(req.query['ts-rest']) ? req.query['ts-rest'] : []; // Make `ts-rest` an array. + req.query['ts-rest'].unshift('api', 'v1'); // Prepend our base path to the array. + + return await nextRouteHandler(req, res); +} diff --git a/apps/web/src/pages/api/v1/openapi.json.ts b/apps/web/src/pages/api/v1/openapi.json.ts new file mode 100644 index 000000000..e3ea15051 --- /dev/null +++ b/apps/web/src/pages/api/v1/openapi.json.ts @@ -0,0 +1,7 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +import { OpenAPIV1 } from '@documenso/api/v1/openapi'; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json(OpenAPIV1); +} diff --git a/package-lock.json b/package-lock.json index 3c136e801..19cd71e93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,6 +130,7 @@ "version": "1.2.3", "license": "AGPL-3.0", "dependencies": { + "@documenso/api": "*", "@documenso/assets": "*", "@documenso/ee": "*", "@documenso/lib": "*", @@ -250,6 +251,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@anatine/zod-openapi": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@anatine/zod-openapi/-/zod-openapi-1.14.2.tgz", + "integrity": "sha512-q0qHfnuNYVKu0Swrnnvfj9971AEyW7c8v9jCOZGCl5ZbyGMNG4RPyJkRcMi/JC8CRfdOe0IDfNm1nNsi2avprg==", + "dependencies": { + "ts-deepmerge": "^6.0.3" + }, + "peerDependencies": { + "openapi3-ts": "^2.0.0 || ^3.0.0", + "zod": "^3.20.0" + } + }, "node_modules/@aws-crypto/crc32": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", @@ -1360,6 +1373,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.8.tgz", + "integrity": "sha512-2ZzmcDugdm0/YQKFVYsXiwUN7USPX8PM7cytpb4PFl87fM+qYPSvTZX//8tyeJB1j0YDmafBJEbl5f8NfLyuKw==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -1453,6 +1478,11 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.0.0.tgz", + "integrity": "sha512-GMu2OJiTd1HSe74bbJYQnVvELANpYiGFZELyyTM1CR0sdv5ReQAcJ/c/8pIrPab3lO11+D+EpuGLUxqz+y832g==" + }, "node_modules/@commitlint/cli": { "version": "17.8.1", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.8.1.tgz", @@ -1847,6 +1877,10 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@documenso/api": { + "resolved": "packages/api", + "link": true + }, "node_modules/@documenso/app-tests": { "resolved": "packages/app-tests", "link": true @@ -2102,6 +2136,14 @@ "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==" }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, "node_modules/@floating-ui/core": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", @@ -6224,6 +6266,472 @@ "node": ">=14.0.0" } }, + "node_modules/@swagger-api/apidom-ast": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-0.92.0.tgz", + "integrity": "sha512-j9vuKaYZP3mAGXUcKeWIkSToxPPCBLJcLEfjSEh14P0n6NRJp7Yg19SA+IwHdIvOAfJonuebj/lhPOMjzd6P1g==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-error": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2", + "unraw": "^3.0.0" + } + }, + "node_modules/@swagger-api/apidom-core": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.92.0.tgz", + "integrity": "sha512-PK1zlS0UCcE5dIPtSy8/+oWfXAVf7b/iM3LRaPgaFGF5b8qa6S/zmROTh10Yjug9v9Vnuq8opEhyHkGyl+WdSA==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@types/ramda": "~0.29.6", + "minim": "~0.23.8", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "short-unique-id": "^5.0.2", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-error": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-0.92.0.tgz", + "integrity": "sha512-wo7xCvTpWr5Lpt/ly1L4bhZ6W7grgtAg7SK/d8FNZR85zPJXM4FPMpcRtKktfWJ/RikQJT/g5DjI33iTqB6z/w==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "node_modules/@swagger-api/apidom-json-pointer": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.92.0.tgz", + "integrity": "sha512-VmZ1EXE7BWX+ndeeh9t1uFRql5jbPRmAcglUfdtu3jlg6fOqXzzgx9qFpRz9GhpMHWEGFm1ymd8tMAa1CvgcHw==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-ns-api-design-systems": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-0.92.0.tgz", + "integrity": "sha512-wXEXhw0wDQIPTUqff953h44oQZr29DcoAzZfROWlGtOLItGDDMjhfIYiRg1406mXA4N7d5d0vNi9V/HXkxItQw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-0.92.0.tgz", + "integrity": "sha512-FmJLT3GqzT4HK7Mwh54cXZ4PZt58yKVtJAKWKJ0dg2/Gim0AKJWf6t6B3Z9ZFUiKyehbqP4K7gSM7qGL0tKe2Q==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-json-schema-draft-7": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.92.0.tgz", + "integrity": "sha512-7s2EKjCQwRXbK4Y4AGpVkyn1AANCxOUFSHebo1h2katyVeAopV0LJmbXH5yQedTltV0k3BIjnd7hS+7dI846Pw==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^0.92.0", + "@swagger-api/apidom-core": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-0.92.0.tgz", + "integrity": "sha512-zur80x04jesXVzlU9sLZhW4giO9RfOouI7L/H8v2wUlcBvjaPBn1tIqrURw2VEHKAcJORhTRusQCR21vnFot2g==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-0.92.0.tgz", + "integrity": "sha512-DSY7lY98XHnc0wg0V38ZmBPs5HWuRuSb6G+n5Z+qs5RRodh1x5BrTIY6M0Yk3oJVbbEoFGmF0VlTe6vHf44pbw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@swagger-api/apidom-ns-json-schema-draft-6": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-0.92.0.tgz", + "integrity": "sha512-OJlSTvPzK+zqzd2xXeWkF50z08Wlpygc98eVzZjYI0Af8mz7x6R5T9BCP5p6ZlQoO9OTvk4gfv7ViWXCdamObg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-0": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.92.0.tgz", + "integrity": "sha512-VGha4RRnoeoAZBWLGy37YsBzwICM3ZFNyCk2Dwpaqfg9zFN+E6BL2CtIbkxvFkMdwaMURmDItiQsw28pF0tOgQ==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-1": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.92.0.tgz", + "integrity": "sha512-xZD+JxifYhDoTjn76K2ZT3xNoXBQChaKfSkJr4l5Xh9Guuk0IcsPTUDRpuytuZZXVez0O401XFoUso/mZRTjkA==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^0.92.0", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-ns-workflows-1": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-0.92.0.tgz", + "integrity": "sha512-gl1dF+SrRHK4lLiwaK4PMjL9A5z28cW9xiMWCxRyppX/I2bVTVVOfgdAyqLWsFA0gopmITWesJxohRumG35fTw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-0.92.0.tgz", + "integrity": "sha512-i07FeLdNobWzHT9LnfsdOix+XrlZN/KnQL1RODPzxWk7i7ya2e4uc3JemyHh4Tnv04G8JV32SQqtzOtMteJsdA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-api-design-systems": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-0.92.0.tgz", + "integrity": "sha512-bbjFkU0D4zqaZnd8/m1Kyx2UuHpri8ZxLdT1TiXqHweSfRQcNt4VYt0bjWBnnGGBMkHElgYbX5ov6kHvPf3wJg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-api-design-systems": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-0.92.0.tgz", + "integrity": "sha512-Q7gudmGA5TUGbbr0QYNQkndktP91C0WE7uDDS2IwCBtHroRDiMPFCjzE9dsjIST5WnP+LUXmxG1Bv0NLTWcSUg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-asyncapi-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-0.92.0.tgz", + "integrity": "sha512-V5/VdDj0aeOKp+3AtvPSz2b0HosJfYkHPjNvPU5eafLSzqzMIR/evYq5BvKWoJL1IvLdjoEPqDVVaEZluHZTew==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-asyncapi-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-0.92.0.tgz", + "integrity": "sha512-KA1Nn6FN0zTA5JhRazwYN9voTDlmExID7Jwz6GXmY826OXqeT4Yl0Egyo1aLYrfT0S73vhC4LVqpdORWLGdZtg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^0.92.0", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "tree-sitter": "=0.20.4", + "tree-sitter-json": "=0.20.1", + "web-tree-sitter": "=0.20.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-0.92.0.tgz", + "integrity": "sha512-8OlvjcvI/GuOFJJxN+Mc4tJSo9UWuJdzQtQOtO4k3QwWwS28hGvRTjQ5PpsXAVZoLJMAbDuRdREYD9qeIKvM2g==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-0.92.0.tgz", + "integrity": "sha512-kzE4COaNobKIUjGsdqqXgO/LruaQHs2kTzOzHPUTR1TH1ZlB2t8MTV+6LJzGNG3IB3QSfZDd7KBEYWklsCTyTA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-0.92.0.tgz", + "integrity": "sha512-4gkIXfKGwEKZQ6+kxp4EdFBlAc7Kjq8GAgaC7ilGTSSxIaz5hBHBOJoe3cXWpQ/WlXiOyNCy7WdbuKRpUDKIdg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-0.92.0.tgz", + "integrity": "sha512-TIY9cytYhA3yUf+5PcwsH9UjzKy5V4nGUtK6n5RvcL4btaGQA2LUB5CiV/1nSvYLNjYjGxhtB3haZDbHe3/gyw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-0.92.0.tgz", + "integrity": "sha512-AUwtAxeautYtiwifNCmv6Kjs7ksptRFxcQ3sgLv2bP3f9t5jzcI9NhmgJNdbRfohHYaHMwTuUESrfsTdBgKlAA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-0.92.0.tgz", + "integrity": "sha512-gMR4zUZ/RrjVJVr6DnqwsCsnlplGXJk6O9UKbkoBsiom81dkcHx68BmWA2oM2lYVGKx+G8WVmVDo2EJaZvZYGg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-0.92.0.tgz", + "integrity": "sha512-tyLiSxEKeU6mhClFjNxrTQJA2aSgfEF7LJ/ZcJgvREsvyk6ns3op9wN2SXw4UmD+657IgN0aUPihh92aEXKovA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-workflows-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-yaml-1/-/apidom-parser-adapter-workflows-yaml-1-0.92.0.tgz", + "integrity": "sha512-0Nr+5oAocuw3SZXcO8WEqnU7GGWP7O6GrsFafD6KLBL05v3I0erPfmnWQjWh6jBeXv8r5W69WEQItzES0DBJjA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-ns-workflows-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-0.92.0.tgz", + "integrity": "sha512-cFLqlhehMuY5WRdU1780Vno6iWpjMlr7CfOOloZW1rKf2lvojn0c4eDsyfWFaB2DgE+Xd4CWl55McuaPZMngsw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^0.92.0", + "@swagger-api/apidom-core": "^0.92.0", + "@swagger-api/apidom-error": "^0.92.0", + "@types/ramda": "~0.29.6", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "tree-sitter": "=0.20.4", + "tree-sitter-yaml": "=0.5.0", + "web-tree-sitter": "=0.20.3" + } + }, + "node_modules/@swagger-api/apidom-reference": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.92.0.tgz", + "integrity": "sha512-G/qJBTpXCdwPsc5dqPjX+vAfhvtnhIFqnKtEZ71wnEvF7TpIxdeZKKfqpg+Zxi7MSuZD/Gpkr4J/eP0lO0fAdA==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^0.92.0", + "@types/ramda": "~0.29.6", + "axios": "^1.4.0", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.29.1", + "ramda-adjunct": "^4.1.1", + "stampit": "^4.3.2" + }, + "optionalDependencies": { + "@swagger-api/apidom-error": "^0.92.0", + "@swagger-api/apidom-json-pointer": "^0.92.0", + "@swagger-api/apidom-ns-asyncapi-2": "^0.92.0", + "@swagger-api/apidom-ns-openapi-2": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.92.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.92.0", + "@swagger-api/apidom-ns-workflows-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.92.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.92.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-json": "^0.92.0", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.92.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^0.92.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.92.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-workflows-json-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-workflows-yaml-1": "^0.92.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.92.0" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@swc/helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", @@ -6392,6 +6900,31 @@ "node": ">=18.0.0" } }, + "node_modules/@ts-rest/core": { + "version": "3.30.5", + "resolved": "https://registry.npmjs.org/@ts-rest/core/-/core-3.30.5.tgz", + "integrity": "sha512-j2sgvk3x8wZiCyhB3ij0I287FgkngCGRHXFBxQ9HtZ9mxQuIIDfibi1yD/ydNvNif0pA6BDdASGQY1WjfqUC3g==", + "peerDependencies": { + "zod": "^3.22.3" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ts-rest/open-api": { + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/@ts-rest/open-api/-/open-api-3.33.0.tgz", + "integrity": "sha512-ZUhOWy7oIo9D53W4/DuJuum6RtwSrcxr7VrNTKOeUlq+uvx8yzW/cxaZEh/SrHnzXhOegWj+lWRCH32MS/CaNA==", + "dependencies": { + "@anatine/zod-openapi": "^1.12.0", + "openapi3-ts": "^2.0.2" + }, + "peerDependencies": { + "zod": "^3.22.3" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -6615,14 +7148,20 @@ "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "devOptional": true + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/ramda": { + "version": "0.29.9", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.9.tgz", + "integrity": "sha512-X3yEG6tQCWBcUAql+RPC/O1Hm9BSU+MXu2wJnCETuAgUlrEDwTA1kIOdEEE4YXDtf0zfQLHa9CCE7WYp9kqPIQ==", + "dependencies": { + "types-ramda": "^0.29.6" + } }, "node_modules/@types/react": { "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz", "integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -6646,14 +7185,21 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "devOptional": true + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" }, + "node_modules/@types/swagger-ui-react": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.18.3.tgz", + "integrity": "sha512-Mo/R7IjDVwtiFPs84pWvh5pI9iyNGBjmfielxqbOh2Jv+8WVSDVe8Nu25kb5BOuV2xmGS3o33jr6nwDJMBcX+Q==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/ua-parser-js": { "version": "0.7.39", "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", @@ -6665,6 +7211,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -6872,6 +7423,11 @@ "resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.117.0.tgz", "integrity": "sha512-vZkfoag1kHqItK/zebxT0Fkt3R/zscjgD+Ib7kaAdum0Sz9psXDfVHPW1Benv91d02zPWlLIvZtjBmzX4a+6fw==" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -7246,6 +7802,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -7254,6 +7818,14 @@ "node": ">=4" } }, + "node_modules/autolinker": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", + "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", + "dependencies": { + "tslib": "^2.3.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.16", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", @@ -7813,6 +8385,20 @@ "node": ">=10" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, "node_modules/class-variance-authority": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.6.1.tgz", @@ -7824,6 +8410,11 @@ "url": "https://joebell.co.uk" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -8472,6 +9063,24 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js-pure": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.0.tgz", + "integrity": "sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -8542,6 +9151,11 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -8556,8 +9170,7 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "devOptional": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/d3-array": { "version": "3.2.4", @@ -9052,6 +9665,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz", + "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -9110,6 +9728,14 @@ "node": ">=12" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "engines": { + "node": ">=4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -10486,6 +11112,11 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -10617,6 +11248,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -11485,6 +12124,14 @@ "node": ">=8" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -11676,6 +12323,14 @@ "node": ">=14.0.0" } }, + "node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -11943,6 +12598,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -12267,6 +12936,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -12445,6 +13125,11 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==" + }, "node_modules/js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -12502,6 +13187,23 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -12529,6 +13231,14 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -12584,6 +13294,14 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -12884,6 +13602,11 @@ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", "dev": true }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", @@ -13037,6 +13760,31 @@ "tslib": "^2.0.3" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lowlight/node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -14077,6 +14825,17 @@ "node": ">=4" } }, + "node_modules/minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "dependencies": { + "lodash": "^4.15.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -14439,6 +15198,11 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -14479,6 +15243,22 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -14760,6 +15540,37 @@ "node": ">= 14.17.0" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi3-ts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz", + "integrity": "sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==", + "dependencies": { + "yaml": "^1.10.2" + } + }, + "node_modules/openapi3-ts/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/openid-client": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", @@ -14849,6 +15660,14 @@ "node": ">=8" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/oslo": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/oslo/-/oslo-0.17.0.tgz", @@ -14983,6 +15802,68 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15557,6 +16438,14 @@ "node": ">=16.13" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -15667,6 +16556,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -15700,6 +16594,30 @@ "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==" }, + "node_modules/ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/ramda-adjunct": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-4.1.1.tgz", + "integrity": "sha512-BnCGsZybQZMDGram9y7RiryoRHS5uwx8YeGuUeDKuZuvK38XO6JJfmK85BwRWAKFA6pZ5nZBO/HBFtExVaf31w==", + "engines": { + "node": ">=0.10.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda-adjunct" + }, + "peerDependencies": { + "ramda": ">= 0.29.0" + } + }, "node_modules/randexp": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", @@ -15712,6 +16630,14 @@ "node": ">=0.12" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/raw-body": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", @@ -15763,6 +16689,18 @@ "react": "^16.3.0 || ^17.0.1 || ^18.0.0" } }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-day-picker": { "version": "8.9.1", "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.9.1.tgz", @@ -15776,6 +16714,18 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-debounce-input": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", + "integrity": "sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==", + "dependencies": { + "lodash.debounce": "^4", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -16054,6 +17004,35 @@ "react": "*" } }, + "node_modules/react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "dependencies": { + "invariant": "^2.2.2" + }, + "peerDependencies": { + "immutable": ">=3.6.2" + } + }, + "node_modules/react-immutable-pure-component": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz", + "integrity": "sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==", + "peerDependencies": { + "immutable": ">= 2 || >= 4.0.0-rc", + "react": ">= 16.6", + "react-dom": ">= 16.6" + } + }, + "node_modules/react-inspector": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -16213,6 +17192,21 @@ } } }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/react-transition-group": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", @@ -16494,6 +17488,19 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", + "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "peerDependencies": { + "immutable": "^3.8.1 || ^4.0.0-rc.1" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -16513,6 +17520,167 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/refractor/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", @@ -16633,6 +17801,29 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "dependencies": { + "argparse": "^1.0.10", + "autolinker": "^3.11.0" + }, + "bin": { + "remarkable": "bin/remarkable.js" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/remarkable/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/remeda": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.29.0.tgz", @@ -16671,6 +17862,16 @@ "node": ">=0.10.5" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resend": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resend/-/resend-2.0.0.tgz", @@ -16990,6 +18191,31 @@ "node": ">=10" } }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -17032,6 +18258,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -17067,6 +18305,15 @@ "node": ">=4" } }, + "node_modules/short-unique-id": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.0.3.tgz", + "integrity": "sha512-yhniEILouC0s4lpH0h7rJsfylZdca10W9mDJRAFh3EpcSUanCHGb0R7kcFOIUCZYSAPo0PUD5ZxWQdW0T4xaug==", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -17319,6 +18566,11 @@ "sql-formatter": "bin/sql-formatter-cli.cjs" } }, + "node_modules/stampit": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stampit/-/stampit-4.3.2.tgz", + "integrity": "sha512-pE2org1+ZWQBnIxRPrBM2gVupkuDD0TTNIo1H6GdT/vO82NXli2z8lRE8cu/nBIHrcOCXFBAHpb9ZldrB2/qOA==" + }, "node_modules/start-server-and-test": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.3.tgz", @@ -17749,6 +19001,151 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-client": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.25.0.tgz", + "integrity": "sha512-p143zWkIhgyh2E5+3HPFMlCw3WkV9RbX9HyftfBdiccCbOlmHdcJC0XEJZxcm+ZA+80DORs0F30/mzk7sx4iwA==", + "dependencies": { + "@babel/runtime-corejs3": "^7.22.15", + "@swagger-api/apidom-core": ">=0.90.0 <1.0.0", + "@swagger-api/apidom-error": ">=0.90.0 <1.0.0", + "@swagger-api/apidom-json-pointer": ">=0.90.0 <1.0.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=0.90.0 <1.0.0", + "@swagger-api/apidom-reference": ">=0.90.0 <1.0.0", + "cookie": "~0.6.0", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "is-plain-object": "^5.0.0", + "js-yaml": "^4.1.0", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.1", + "qs": "^6.10.2", + "traverse": "~0.6.6", + "undici": "^5.24.0" + } + }, + "node_modules/swagger-client/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/swagger-client/node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-ui-react": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.11.0.tgz", + "integrity": "sha512-iqc5/Z8nvqOdjU2LuWYbREnDmKj5gndZSESTH9dXfymlzLc2NoPQmXZAw02U8kFgHyciX0yDMp3oaCw1zBdPSA==", + "dependencies": { + "@babel/runtime-corejs3": "^7.23.7", + "@braintree/sanitize-url": "=7.0.0", + "base64-js": "^1.5.1", + "classnames": "^2.5.1", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "=3.0.6", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "=4.1.0", + "lodash": "^4.17.21", + "patch-package": "^8.0.0", + "prop-types": "^15.8.1", + "randexp": "^0.5.3", + "randombytes": "^2.1.0", + "react-copy-to-clipboard": "5.1.0", + "react-debounce-input": "=3.3.0", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^2.2.0", + "react-inspector": "^6.0.1", + "react-redux": "^9.0.4", + "react-syntax-highlighter": "^15.5.0", + "redux": "^5.0.0", + "redux-immutable": "^4.0.0", + "remarkable": "^2.0.1", + "reselect": "^5.0.1", + "serialize-error": "^8.1.0", + "sha.js": "^2.4.11", + "swagger-client": "^3.25.0", + "url-parse": "^1.5.10", + "xml": "=1.0.1", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0 <19", + "react-dom": ">=16.8.0 <19" + } + }, + "node_modules/swagger-ui-react/node_modules/@types/react": { + "version": "18.2.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", + "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/swagger-ui-react/node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/swagger-ui-react/node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/swagger-ui-react/node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/tailwind-merge": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", @@ -17920,6 +19317,17 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -17939,6 +19347,11 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -17996,6 +19409,37 @@ "node": ">= 6" } }, + "node_modules/tree-sitter": { + "version": "0.20.4", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.20.4.tgz", + "integrity": "sha512-rjfR5dc4knG3jnJNN/giJ9WOoN1zL/kZyrS0ILh+eqq8RNcIbiXA63JsMEgluug0aNvfQvK4BfCErN1vIzvKog==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.17.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/tree-sitter-json": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.20.1.tgz", + "integrity": "sha512-482hf7J+aBwhksSw8yWaqI8nyP1DrSwnS4IMBShsnkFWD3SE8oalHnsEik59fEVi3orcTCUtMzSjZx+0Tpa6Vw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.18.0" + } + }, + "node_modules/tree-sitter-yaml": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tree-sitter-yaml/-/tree-sitter-yaml-0.5.0.tgz", + "integrity": "sha512-POJ4ZNXXSWIG/W4Rjuyg36MkUD4d769YRUGKRqN+sVaj/VCo6Dh6Pkssn1Rtewd5kybx+jT1BWMyWN0CijXnMA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.14.0" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -18034,6 +19478,14 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-deepmerge": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-6.2.0.tgz", + "integrity": "sha512-2qxI/FZVDPbzh63GwWIZYE7daWKtwXZYuyc8YNq0iTmMUwn4mL0jRLsp6hfFlgbdRSR4x2ppe+E86FnvEpN7Nw==", + "engines": { + "node": ">=14.13.1" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -18087,6 +19539,11 @@ "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.0.5.tgz", "integrity": "sha512-tL0w8U/pgaacOmkb9fRlYzWEUDCfVjjv9dD4wHTgZ61MjhuMt46VNWTG747NqW6vRzoWIKABVhFSOJ82FvXrfA==" }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -18549,6 +20006,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/types-ramda": { + "version": "0.29.6", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.29.6.tgz", + "integrity": "sha512-VJoOk1uYNh9ZguGd3eZvqkdhD4hTGtnjRBUx5Zc0U9ftmnCgiWcSj/lsahzKunbiwRje1MxxNkEy1UdcXRCpYw==", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", @@ -18597,6 +20062,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "5.28.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", + "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -18744,6 +20220,11 @@ "node": ">= 0.8" } }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" + }, "node_modules/unzipper": { "version": "0.10.14", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", @@ -18840,6 +20321,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", @@ -19059,6 +20549,12 @@ "node": ">= 8" } }, + "node_modules/web-tree-sitter": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.3.tgz", + "integrity": "sha512-zKGJW9r23y3BcJusbgvnOH2OYAW40MXAOi9bi3Gcc7T4Gms9WWgXF8m6adsJWpGJEhgOzCrfiz1IzKowJWrtYw==", + "optional": true + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -19321,6 +20817,19 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "node_modules/xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "dependencies": { + "repeat-string": "^1.5.2" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -19430,6 +20939,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zenscroll": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", + "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==" + }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -19447,6 +20961,235 @@ "url": "https://github.com/sponsors/wooorm" } }, + "packages/api": { + "name": "@documenso/api", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@documenso/lib": "*", + "@documenso/prisma": "*", + "@ts-rest/core": "^3.30.5", + "@ts-rest/next": "^3.30.5", + "@ts-rest/open-api": "^3.33.0", + "@types/swagger-ui-react": "^4.18.3", + "luxon": "^3.4.0", + "superjson": "^1.13.1", + "swagger-ui-react": "^5.11.0", + "ts-pattern": "^5.0.5", + "zod": "^3.22.4" + } + }, + "packages/api/node_modules/@next/env": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz", + "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==", + "peer": true + }, + "packages/api/node_modules/@next/swc-darwin-arm64": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz", + "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-darwin-x64": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", + "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", + "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-linux-arm64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", + "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-linux-x64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", + "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-linux-x64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", + "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", + "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", + "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@next/swc-win32-x64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", + "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/api/node_modules/@ts-rest/next": { + "version": "3.30.5", + "resolved": "https://registry.npmjs.org/@ts-rest/next/-/next-3.30.5.tgz", + "integrity": "sha512-NasfUN7SnwcjJNbxvvcemC4fOv4f4IF5I14wVqQODN0HWPokkrta6XLuv0eKQJYdB32AS7VINQhls8Sj1AIN0g==", + "peerDependencies": { + "@ts-rest/core": "3.30.5", + "next": "^12.0.0 || ^13.0.0", + "zod": "^3.22.3" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "packages/api/node_modules/next": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz", + "integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==", + "peer": true, + "dependencies": { + "@next/env": "13.5.6", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=16.14.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "13.5.6", + "@next/swc-darwin-x64": "13.5.6", + "@next/swc-linux-arm64-gnu": "13.5.6", + "@next/swc-linux-arm64-musl": "13.5.6", + "@next/swc-linux-x64-gnu": "13.5.6", + "@next/swc-linux-x64-musl": "13.5.6", + "@next/swc-win32-arm64-msvc": "13.5.6", + "@next/swc-win32-ia32-msvc": "13.5.6", + "@next/swc-win32-x64-msvc": "13.5.6" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, "packages/app-tests": { "name": "@documenso/app-tests", "version": "1.0.0", @@ -19762,6 +21505,8 @@ "@trpc/next": "^10.36.0", "@trpc/react-query": "^10.36.0", "@trpc/server": "^10.36.0", + "@ts-rest/core": "^3.30.5", + "@ts-rest/next": "^3.30.5", "luxon": "^3.4.0", "superjson": "^1.13.1", "ts-pattern": "^5.0.5", @@ -19769,6 +21514,217 @@ }, "devDependencies": {} }, + "packages/trpc/node_modules/@next/env": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz", + "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==", + "peer": true + }, + "packages/trpc/node_modules/@next/swc-darwin-arm64": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz", + "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-darwin-x64": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", + "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", + "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-linux-arm64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", + "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-linux-x64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", + "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-linux-x64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", + "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", + "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", + "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@next/swc-win32-x64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", + "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "packages/trpc/node_modules/@ts-rest/next": { + "version": "3.30.5", + "resolved": "https://registry.npmjs.org/@ts-rest/next/-/next-3.30.5.tgz", + "integrity": "sha512-NasfUN7SnwcjJNbxvvcemC4fOv4f4IF5I14wVqQODN0HWPokkrta6XLuv0eKQJYdB32AS7VINQhls8Sj1AIN0g==", + "peerDependencies": { + "@ts-rest/core": "3.30.5", + "next": "^12.0.0 || ^13.0.0", + "zod": "^3.22.3" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "packages/trpc/node_modules/next": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz", + "integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==", + "peer": true, + "dependencies": { + "@next/env": "13.5.6", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=16.14.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "13.5.6", + "@next/swc-darwin-x64": "13.5.6", + "@next/swc-linux-arm64-gnu": "13.5.6", + "@next/swc-linux-arm64-musl": "13.5.6", + "@next/swc-linux-x64-gnu": "13.5.6", + "@next/swc-linux-x64-musl": "13.5.6", + "@next/swc-win32-arm64-msvc": "13.5.6", + "@next/swc-win32-ia32-msvc": "13.5.6", + "@next/swc-win32-x64-msvc": "13.5.6" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, "packages/tsconfig": { "name": "@documenso/tsconfig", "version": "0.0.0", diff --git a/package.json b/package.json index 853de1c6b..17a6a53ca 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,6 @@ "apps/*", "packages/*" ], - "dependencies": { - "next-runtime-env": "^3.2.0" - }, "overrides": { "next-auth": { "next": "14.0.3" diff --git a/packages/api/index.ts b/packages/api/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/api/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/api/next.ts b/packages/api/next.ts new file mode 100644 index 000000000..5ac5aab45 --- /dev/null +++ b/packages/api/next.ts @@ -0,0 +1 @@ +export { createNextRouter } from '@ts-rest/next'; diff --git a/packages/api/package.json b/packages/api/package.json new file mode 100644 index 000000000..aebb09c9b --- /dev/null +++ b/packages/api/package.json @@ -0,0 +1,30 @@ +{ + "name": "@documenso/api", + "version": "1.0.0", + "main": "./index.ts", + "types": "./index.ts", + "license": "MIT", + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "clean": "rimraf node_modules" + }, + "files": [ + "index.ts", + "next.ts", + "v1/" + ], + "dependencies": { + "@documenso/lib": "*", + "@documenso/prisma": "*", + "@ts-rest/core": "^3.30.5", + "@ts-rest/next": "^3.30.5", + "@ts-rest/open-api": "^3.33.0", + "@types/swagger-ui-react": "^4.18.3", + "luxon": "^3.4.0", + "superjson": "^1.13.1", + "swagger-ui-react": "^5.11.0", + "ts-pattern": "^5.0.5", + "zod": "^3.22.4" + } +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json new file mode 100644 index 000000000..dc21318a7 --- /dev/null +++ b/packages/api/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@documenso/tsconfig/react-library.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"], + "compilerOptions": { + "strict": true, + } +} diff --git a/packages/api/v1/api-documentation.tsx b/packages/api/v1/api-documentation.tsx new file mode 100644 index 000000000..6082d2d7f --- /dev/null +++ b/packages/api/v1/api-documentation.tsx @@ -0,0 +1,10 @@ +'use client'; + +import SwaggerUI from 'swagger-ui-react'; +import 'swagger-ui-react/swagger-ui.css'; + +import { OpenAPIV1 } from '@documenso/api/v1/openapi'; + +export const OpenApiDocsPage = () => { + return ; +}; diff --git a/packages/api/v1/contract.ts b/packages/api/v1/contract.ts new file mode 100644 index 000000000..cc13f95d4 --- /dev/null +++ b/packages/api/v1/contract.ts @@ -0,0 +1,191 @@ +import { initContract } from '@ts-rest/core'; + +import { + ZSendDocumentForSigningMutationSchema as SendDocumentMutationSchema, + ZAuthorizationHeadersSchema, + ZCreateDocumentFromTemplateMutationResponseSchema, + ZCreateDocumentFromTemplateMutationSchema, + ZCreateDocumentMutationResponseSchema, + ZCreateDocumentMutationSchema, + ZCreateFieldMutationSchema, + ZCreateRecipientMutationSchema, + ZDeleteDocumentMutationSchema, + ZDeleteFieldMutationSchema, + ZDeleteRecipientMutationSchema, + ZGetDocumentsQuerySchema, + ZSuccessfulDocumentResponseSchema, + ZSuccessfulFieldResponseSchema, + ZSuccessfulGetDocumentResponseSchema, + ZSuccessfulRecipientResponseSchema, + ZSuccessfulResponseSchema, + ZSuccessfulSigningResponseSchema, + ZUnsuccessfulResponseSchema, + ZUpdateFieldMutationSchema, + ZUpdateRecipientMutationSchema, +} from './schema'; + +const c = initContract(); + +export const ApiContractV1 = c.router( + { + getDocuments: { + method: 'GET', + path: '/api/v1/documents', + query: ZGetDocumentsQuerySchema, + responses: { + 200: ZSuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + }, + summary: 'Get all documents', + }, + + getDocument: { + method: 'GET', + path: '/api/v1/documents/:id', + responses: { + 200: ZSuccessfulGetDocumentResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + }, + summary: 'Get a single document', + }, + + createDocument: { + method: 'POST', + path: '/api/v1/documents', + body: ZCreateDocumentMutationSchema, + responses: { + 200: ZCreateDocumentMutationResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + }, + summary: 'Upload a new document and get a presigned URL', + }, + + createDocumentFromTemplate: { + method: 'POST', + path: '/api/v1/templates/:templateId/create-document', + body: ZCreateDocumentFromTemplateMutationSchema, + responses: { + 200: ZCreateDocumentFromTemplateMutationResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + }, + summary: 'Upload a new document and get a presigned URL', + }, + + sendDocument: { + method: 'POST', + path: '/api/v1/documents/:id/send', + body: SendDocumentMutationSchema, + responses: { + 200: ZSuccessfulSigningResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Send a document for signing', + }, + + deleteDocument: { + method: 'DELETE', + path: '/api/v1/documents/:id', + body: ZDeleteDocumentMutationSchema, + responses: { + 200: ZSuccessfulDocumentResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + }, + summary: 'Delete a document', + }, + + createRecipient: { + method: 'POST', + path: '/api/v1/documents/:id/recipients', + body: ZCreateRecipientMutationSchema, + responses: { + 200: ZSuccessfulRecipientResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Create a recipient for a document', + }, + + updateRecipient: { + method: 'PATCH', + path: '/api/v1/documents/:id/recipients/:recipientId', + body: ZUpdateRecipientMutationSchema, + responses: { + 200: ZSuccessfulRecipientResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Update a recipient for a document', + }, + + deleteRecipient: { + method: 'DELETE', + path: '/api/v1/documents/:id/recipients/:recipientId', + body: ZDeleteRecipientMutationSchema, + responses: { + 200: ZSuccessfulRecipientResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Delete a recipient from a document', + }, + + createField: { + method: 'POST', + path: '/api/v1/documents/:id/fields', + body: ZCreateFieldMutationSchema, + responses: { + 200: ZSuccessfulFieldResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Create a field for a document', + }, + + updateField: { + method: 'PATCH', + path: '/api/v1/documents/:id/fields/:fieldId', + body: ZUpdateFieldMutationSchema, + responses: { + 200: ZSuccessfulFieldResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Update a field for a document', + }, + + deleteField: { + method: 'DELETE', + path: '/api/v1/documents/:id/fields/:fieldId', + body: ZDeleteFieldMutationSchema, + responses: { + 200: ZSuccessfulFieldResponseSchema, + 400: ZUnsuccessfulResponseSchema, + 401: ZUnsuccessfulResponseSchema, + 404: ZUnsuccessfulResponseSchema, + 500: ZUnsuccessfulResponseSchema, + }, + summary: 'Delete a field from a document', + }, + }, + { + baseHeaders: ZAuthorizationHeadersSchema, + }, +); diff --git a/packages/api/v1/examples/01-create-and-send-document.ts b/packages/api/v1/examples/01-create-and-send-document.ts new file mode 100644 index 000000000..925d86656 --- /dev/null +++ b/packages/api/v1/examples/01-create-and-send-document.ts @@ -0,0 +1,59 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const { status, body } = await client.createDocument({ + body: { + title: 'My Document', + recipients: [ + { + name: 'John Doe', + email: 'john@example.com', + role: 'SIGNER', + }, + { + name: 'Jane Doe', + email: 'jane@example.com', + role: 'APPROVER', + }, + ], + meta: { + subject: 'Please sign this document', + message: 'Hey {signer.name}, please sign the following document: {document.name}', + }, + }, + }); + + if (status !== 200) { + throw new Error('Failed to create document'); + } + + const { uploadUrl, documentId } = body; + + await fetch(uploadUrl, { + method: 'PUT', + headers: { + 'Content-Type': 'application/octet-stream', + }, + body: '', + }); + + await client.sendDocument({ + params: { + id: documentId.toString(), + }, + }); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/02-add-a-field.ts b/packages/api/v1/examples/02-add-a-field.ts new file mode 100644 index 000000000..6b186694a --- /dev/null +++ b/packages/api/v1/examples/02-add-a-field.ts @@ -0,0 +1,43 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + const recipientId = 1; + + const { status, body } = await client.createField({ + params: { + id: documentId, + }, + body: { + type: 'SIGNATURE', + pageHeight: 2.5, // percent of page to occupy in height + pageWidth: 5, // percent of page to occupy in width + pageX: 10, // percent from left + pageY: 10, // percent from top + pageNumber: 1, + recipientId, + }, + }); + + if (status !== 200) { + throw new Error('Failed to create field'); + } + + const { id: fieldId } = body; + + console.log(`Field created with id: ${fieldId}`); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/03-update-a-field.ts b/packages/api/v1/examples/03-update-a-field.ts new file mode 100644 index 000000000..d93831b7c --- /dev/null +++ b/packages/api/v1/examples/03-update-a-field.ts @@ -0,0 +1,39 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + const fieldId = '1'; + + const { status } = await client.updateField({ + params: { + id: documentId, + fieldId, + }, + body: { + type: 'SIGNATURE', + pageHeight: 2.5, // percent of page to occupy in height + pageWidth: 5, // percent of page to occupy in width + pageX: 10, // percent from left + pageY: 10, // percent from top + pageNumber: 1, + }, + }); + + if (status !== 200) { + throw new Error('Failed to update field'); + } +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/04-remove-a-field.ts b/packages/api/v1/examples/04-remove-a-field.ts new file mode 100644 index 000000000..d7f233940 --- /dev/null +++ b/packages/api/v1/examples/04-remove-a-field.ts @@ -0,0 +1,31 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + const fieldId = '1'; + + const { status } = await client.deleteField({ + params: { + id: documentId, + fieldId, + }, + }); + + if (status !== 200) { + throw new Error('Failed to remove field'); + } +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/05-add-a-recipient.ts b/packages/api/v1/examples/05-add-a-recipient.ts new file mode 100644 index 000000000..e63abd9e5 --- /dev/null +++ b/packages/api/v1/examples/05-add-a-recipient.ts @@ -0,0 +1,38 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + + const { status, body } = await client.createRecipient({ + params: { + id: documentId, + }, + body: { + name: 'John Doe', + email: 'john@example.com', + role: 'APPROVER', + }, + }); + + if (status !== 200) { + throw new Error('Failed to add recipient'); + } + + const { id: recipientId } = body; + + console.log(`Recipient added with id: ${recipientId}`); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/06-update-a-recipient.ts b/packages/api/v1/examples/06-update-a-recipient.ts new file mode 100644 index 000000000..d9e8255e7 --- /dev/null +++ b/packages/api/v1/examples/06-update-a-recipient.ts @@ -0,0 +1,34 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + const recipientId = '1'; + + const { status } = await client.updateRecipient({ + params: { + id: documentId, + recipientId, + }, + body: { + name: 'Johnathon Doe', + }, + }); + + if (status !== 200) { + throw new Error('Failed to update recipient'); + } +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/07-remove-a-recipient.ts b/packages/api/v1/examples/07-remove-a-recipient.ts new file mode 100644 index 000000000..956e7dcae --- /dev/null +++ b/packages/api/v1/examples/07-remove-a-recipient.ts @@ -0,0 +1,31 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + const recipientId = '1'; + + const { status } = await client.deleteRecipient({ + params: { + id: documentId, + recipientId, + }, + }); + + if (status !== 200) { + throw new Error('Failed to update recipient'); + } +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/08-get-a-document.ts b/packages/api/v1/examples/08-get-a-document.ts new file mode 100644 index 000000000..eb69cc8e8 --- /dev/null +++ b/packages/api/v1/examples/08-get-a-document.ts @@ -0,0 +1,31 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const documentId = '1'; + + const { status, body } = await client.getDocument({ + params: { + id: documentId, + }, + }); + + if (status !== 200) { + throw new Error('Failed to get document'); + } + + console.log(`Got document with id: ${documentId} and title: ${body.title}`); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/examples/09-paginate-all-documents.ts b/packages/api/v1/examples/09-paginate-all-documents.ts new file mode 100644 index 000000000..f0330b620 --- /dev/null +++ b/packages/api/v1/examples/09-paginate-all-documents.ts @@ -0,0 +1,37 @@ +import { initClient } from '@ts-rest/core'; + +import { ApiContractV1 } from '../contract'; + +const main = async () => { + const client = initClient(ApiContractV1, { + baseUrl: 'http://localhost:3000/api/v1', + baseHeaders: { + authorization: 'Bearer ', + }, + }); + + const page = 1; + const perPage = 10; + + const { status, body } = await client.getDocuments({ + query: { + page, + perPage, + }, + }); + + if (status !== 200) { + throw new Error('Failed to get documents'); + } + + for (const document of body.documents) { + console.log(`Got document with id: ${document.id} and title: ${document.title}`); + } + + console.log(`Total documents: ${body.totalPages * perPage}`); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts new file mode 100644 index 000000000..89d211bb0 --- /dev/null +++ b/packages/api/v1/implementation.ts @@ -0,0 +1,720 @@ +import { createNextRoute } from '@ts-rest/next'; + +import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data'; +import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta'; +import { createDocument } from '@documenso/lib/server-only/document/create-document'; +import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; +import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; +import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; +import { sendDocument } from '@documenso/lib/server-only/document/send-document'; +import { updateDocument } from '@documenso/lib/server-only/document/update-document'; +import { createField } from '@documenso/lib/server-only/field/create-field'; +import { deleteField } from '@documenso/lib/server-only/field/delete-field'; +import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id'; +import { updateField } from '@documenso/lib/server-only/field/update-field'; +import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient'; +import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recipient-by-id'; +import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document'; +import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document'; +import { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient'; +import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template'; +import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions'; +import { DocumentDataType, DocumentStatus, SigningStatus } from '@documenso/prisma/client'; + +import { ApiContractV1 } from './contract'; +import { authenticatedMiddleware } from './middleware/authenticated'; + +export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { + getDocuments: authenticatedMiddleware(async (args, user) => { + const page = Number(args.query.page) || 1; + const perPage = Number(args.query.perPage) || 10; + + const { data: documents, totalPages } = await findDocuments({ page, perPage, userId: user.id }); + + return { + status: 200, + body: { + documents, + totalPages, + }, + }; + }), + + getDocument: authenticatedMiddleware(async (args, user) => { + const { id: documentId } = args.params; + + try { + const document = await getDocumentById({ id: Number(documentId), userId: user.id }); + const recipients = await getRecipientsForDocument({ + documentId: Number(documentId), + userId: user.id, + }); + + return { + status: 200, + body: { + ...document, + recipients, + }, + }; + } catch (err) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + }), + + deleteDocument: authenticatedMiddleware(async (args, user) => { + const { id: documentId } = args.params; + + try { + const document = await getDocumentById({ id: Number(documentId), userId: user.id }); + + const deletedDocument = await deleteDocument({ + id: Number(documentId), + userId: user.id, + status: document.status, + }); + + return { + status: 200, + body: deletedDocument, + }; + } catch (err) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + }), + + createDocument: authenticatedMiddleware(async (args, user) => { + const { body } = args; + + try { + if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') { + return { + status: 500, + body: { + message: 'Create document is not available without S3 transport.', + }, + }; + } + + const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`; + + const { url, key } = await getPresignPostUrl(fileName, 'application/pdf'); + + const documentData = await createDocumentData({ + data: key, + type: DocumentDataType.S3_PATH, + }); + + const document = await createDocument({ + title: body.title, + userId: user.id, + documentDataId: documentData.id, + }); + + const recipients = await setRecipientsForDocument({ + userId: user.id, + documentId: document.id, + recipients: body.recipients, + }); + + return { + status: 200, + body: { + uploadUrl: url, + documentId: document.id, + recipients: recipients.map((recipient) => ({ + recipientId: recipient.id, + name: recipient.name, + email: recipient.email, + token: recipient.token, + role: recipient.role, + })), + }, + }; + } catch (err) { + return { + status: 404, + body: { + message: 'An error has occured while uploading the file', + }, + }; + } + }), + + createDocumentFromTemplate: authenticatedMiddleware(async (args, user) => { + const { body, params } = args; + + const templateId = Number(params.templateId); + + const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`; + + const document = await createDocumentFromTemplate({ + templateId, + userId: user.id, + recipients: body.recipients, + }); + + await updateDocument({ + documentId: document.id, + userId: user.id, + data: { + title: body.title, + }, + }); + + if (body.meta) { + await upsertDocumentMeta({ + documentId: document.id, + userId: user.id, + subject: body.meta.subject, + message: body.meta.message, + dateFormat: body.meta.dateFormat, + timezone: body.meta.timezone, + }); + } + + return { + status: 200, + body: { + documentId: document.id, + recipients: document.Recipient.map((recipient) => ({ + recipientId: recipient.id, + name: recipient.name, + email: recipient.email, + token: recipient.token, + role: recipient.role, + })), + }, + }; + }), + + sendDocument: authenticatedMiddleware(async (args, user) => { + const { id } = args.params; + + const document = await getDocumentById({ id: Number(id), userId: user.id }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === 'PENDING') { + return { + status: 400, + body: { + message: 'Document is already waiting for signing', + }, + }; + } + + try { + // await setRecipientsForDocument({ + // userId: user.id, + // documentId: Number(id), + // recipients: [ + // { + // email: body.signerEmail, + // name: body.signerName ?? '', + // }, + // ], + // }); + + // await setFieldsForDocument({ + // documentId: Number(id), + // userId: user.id, + // fields: body.fields.map((field) => ({ + // signerEmail: body.signerEmail, + // type: field.fieldType, + // pageNumber: field.pageNumber, + // pageX: field.pageX, + // pageY: field.pageY, + // pageWidth: field.pageWidth, + // pageHeight: field.pageHeight, + // })), + // }); + + // if (body.emailBody || body.emailSubject) { + // await upsertDocumentMeta({ + // documentId: Number(id), + // subject: body.emailSubject ?? '', + // message: body.emailBody ?? '', + // }); + // } + + await sendDocument({ + documentId: Number(id), + userId: user.id, + }); + + return { + status: 200, + body: { + message: 'Document sent for signing successfully', + }, + }; + } catch (err) { + return { + status: 500, + body: { + message: 'An error has occured while sending the document for signing', + }, + }; + } + }), + + createRecipient: authenticatedMiddleware(async (args, user) => { + const { id: documentId } = args.params; + const { name, email, role } = args.body; + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is already completed', + }, + }; + } + + const recipients = await getRecipientsForDocument({ + documentId: Number(documentId), + userId: user.id, + }); + + const recipientAlreadyExists = recipients.some((recipient) => recipient.email === email); + + if (recipientAlreadyExists) { + return { + status: 400, + body: { + message: 'Recipient already exists', + }, + }; + } + + try { + const newRecipients = await setRecipientsForDocument({ + documentId: Number(documentId), + userId: user.id, + recipients: [ + ...recipients, + { + email, + name, + role, + }, + ], + }); + + const newRecipient = newRecipients.find((recipient) => recipient.email === email); + + if (!newRecipient) { + throw new Error('Recipient not found'); + } + + return { + status: 200, + body: { + ...newRecipient, + documentId: Number(documentId), + }, + }; + } catch (err) { + return { + status: 500, + body: { + message: 'An error has occured while creating the recipient', + }, + }; + } + }), + + updateRecipient: authenticatedMiddleware(async (args, user) => { + const { id: documentId, recipientId } = args.params; + const { name, email, role } = args.body; + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is already completed', + }, + }; + } + + const updatedRecipient = await updateRecipient({ + documentId: Number(documentId), + recipientId: Number(recipientId), + email, + name, + role, + }).catch(() => null); + + if (!updatedRecipient) { + return { + status: 404, + body: { + message: 'Recipient not found', + }, + }; + } + + return { + status: 200, + body: { + ...updatedRecipient, + documentId: Number(documentId), + }, + }; + }), + + deleteRecipient: authenticatedMiddleware(async (args, user) => { + const { id: documentId, recipientId } = args.params; + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is already completed', + }, + }; + } + + const deletedRecipient = await deleteRecipient({ + documentId: Number(documentId), + recipientId: Number(recipientId), + }).catch(() => null); + + if (!deletedRecipient) { + return { + status: 400, + body: { + message: 'Unable to delete recipient', + }, + }; + } + + return { + status: 200, + body: { + ...deletedRecipient, + documentId: Number(documentId), + }, + }; + }), + + createField: authenticatedMiddleware(async (args, user) => { + const { id: documentId } = args.params; + const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY } = args.body; + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is already completed', + }, + }; + } + + const recipient = await getRecipientById({ + id: Number(recipientId), + documentId: Number(documentId), + }).catch(() => null); + + if (!recipient) { + return { + status: 404, + body: { + message: 'Recipient not found', + }, + }; + } + + if (recipient.signingStatus === SigningStatus.SIGNED) { + return { + status: 400, + body: { + message: 'Recipient has already signed the document', + }, + }; + } + + const field = await createField({ + documentId: Number(documentId), + recipientId: Number(recipientId), + type, + pageNumber, + pageX, + pageY, + pageWidth, + pageHeight, + }); + + const remappedField = { + id: field.id, + documentId: field.documentId, + recipientId: field.recipientId ?? -1, + type: field.type, + pageNumber: field.page, + pageX: Number(field.positionX), + pageY: Number(field.positionY), + pageWidth: Number(field.width), + pageHeight: Number(field.height), + customText: field.customText, + inserted: field.inserted, + }; + + return { + status: 200, + body: { + ...remappedField, + documentId: Number(documentId), + }, + }; + }), + + updateField: authenticatedMiddleware(async (args, user) => { + const { id: documentId, fieldId } = args.params; + const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY } = args.body; + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is already completed', + }, + }; + } + + const recipient = await getRecipientById({ + id: Number(recipientId), + documentId: Number(documentId), + }).catch(() => null); + + if (!recipient) { + return { + status: 404, + body: { + message: 'Recipient not found', + }, + }; + } + + if (recipient.signingStatus === SigningStatus.SIGNED) { + return { + status: 400, + body: { + message: 'Recipient has already signed the document', + }, + }; + } + + const updatedField = await updateField({ + fieldId: Number(fieldId), + documentId: Number(documentId), + recipientId: recipientId ? Number(recipientId) : undefined, + type, + pageNumber, + pageX, + pageY, + pageWidth, + pageHeight, + }); + + const remappedField = { + id: updatedField.id, + documentId: updatedField.documentId, + recipientId: updatedField.recipientId ?? -1, + type: updatedField.type, + pageNumber: updatedField.page, + pageX: Number(updatedField.positionX), + pageY: Number(updatedField.positionY), + pageWidth: Number(updatedField.width), + pageHeight: Number(updatedField.height), + customText: updatedField.customText, + inserted: updatedField.inserted, + }; + + return { + status: 200, + body: { + ...remappedField, + documentId: Number(documentId), + }, + }; + }), + + deleteField: authenticatedMiddleware(async (args, user) => { + const { id: documentId, fieldId } = args.params; + + const document = await getDocumentById({ + id: Number(documentId), + userId: user.id, + }); + + if (!document) { + return { + status: 404, + body: { + message: 'Document not found', + }, + }; + } + + if (document.status === DocumentStatus.COMPLETED) { + return { + status: 400, + body: { + message: 'Document is already completed', + }, + }; + } + + const field = await getFieldById({ + fieldId: Number(fieldId), + documentId: Number(documentId), + }).catch(() => null); + + if (!field) { + return { + status: 404, + body: { + message: 'Field not found', + }, + }; + } + + const recipient = await getRecipientById({ + id: Number(field.recipientId), + documentId: Number(documentId), + }).catch(() => null); + + if (recipient?.signingStatus === SigningStatus.SIGNED) { + return { + status: 400, + body: { + message: 'Recipient has already signed the document', + }, + }; + } + + const deletedField = await deleteField({ + documentId: Number(documentId), + fieldId: Number(fieldId), + }).catch(() => null); + + if (!deletedField) { + return { + status: 400, + body: { + message: 'Unable to delete field', + }, + }; + } + + const remappedField = { + id: deletedField.id, + documentId: deletedField.documentId, + recipientId: deletedField.recipientId ?? -1, + type: deletedField.type, + pageNumber: deletedField.page, + pageX: Number(deletedField.positionX), + pageY: Number(deletedField.positionY), + pageWidth: Number(deletedField.width), + pageHeight: Number(deletedField.height), + customText: deletedField.customText, + inserted: deletedField.inserted, + }; + + return { + status: 200, + body: { + ...remappedField, + documentId: Number(documentId), + }, + }; + }), +}); diff --git a/packages/api/v1/middleware/authenticated.ts b/packages/api/v1/middleware/authenticated.ts new file mode 100644 index 000000000..391e3a86f --- /dev/null +++ b/packages/api/v1/middleware/authenticated.ts @@ -0,0 +1,41 @@ +import type { NextApiRequest } from 'next'; + +import { getUserByApiToken } from '@documenso/lib/server-only/public-api/get-user-by-token'; +import type { User } from '@documenso/prisma/client'; + +export const authenticatedMiddleware = < + T extends { + req: NextApiRequest; + }, + R extends { + status: number; + body: unknown; + }, +>( + handler: (args: T, user: User) => Promise, +) => { + return async (args: T) => { + try { + const { authorization } = args.req.headers; + + // Support for both "Authorization: Bearer api_xxx" and "Authorization: api_xxx" + const [token] = (authorization || '').split('Bearer ').filter((s) => s.length > 0); + + if (!token) { + throw new Error('Token was not provided for authenticated middleware'); + } + + const user = await getUserByApiToken({ token }); + + return await handler(args, user); + } catch (_err) { + console.log({ _err }); + return { + status: 401, + body: { + message: 'Unauthorized', + }, + } as const; + } + }; +}; diff --git a/packages/api/v1/openapi.ts b/packages/api/v1/openapi.ts new file mode 100644 index 000000000..af0582195 --- /dev/null +++ b/packages/api/v1/openapi.ts @@ -0,0 +1,17 @@ +import { generateOpenApi } from '@ts-rest/open-api'; + +import { ApiContractV1 } from './contract'; + +export const OpenAPIV1 = generateOpenApi( + ApiContractV1, + { + info: { + title: 'Documenso API', + version: '1.0.0', + description: 'The Documenso API for retrieving, creating, updating and deleting documents.', + }, + }, + { + setOperationId: true, + }, +); diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts new file mode 100644 index 000000000..009408927 --- /dev/null +++ b/packages/api/v1/schema.ts @@ -0,0 +1,240 @@ +import { z } from 'zod'; + +import { + FieldType, + ReadStatus, + RecipientRole, + SendStatus, + SigningStatus, +} from '@documenso/prisma/client'; + +/** + * Documents + */ +export const ZGetDocumentsQuerySchema = z.object({ + page: z.coerce.number().min(1).optional().default(1), + perPage: z.coerce.number().min(1).optional().default(1), +}); + +export type TGetDocumentsQuerySchema = z.infer; + +export const ZDeleteDocumentMutationSchema = null; + +export type TDeleteDocumentMutationSchema = typeof ZDeleteDocumentMutationSchema; + +export const ZSuccessfulDocumentResponseSchema = z.object({ + id: z.number(), + userId: z.number(), + title: z.string(), + status: z.string(), + documentDataId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + completedAt: z.date().nullable(), +}); + +export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseSchema.extend({ + recipients: z.lazy(() => z.array(ZSuccessfulRecipientResponseSchema)), +}); + +export type TSuccessfulGetDocumentResponseSchema = z.infer< + typeof ZSuccessfulGetDocumentResponseSchema +>; + +export type TSuccessfulDocumentResponseSchema = z.infer; + +export const ZSendDocumentForSigningMutationSchema = null; + +export type TSendDocumentForSigningMutationSchema = typeof ZSendDocumentForSigningMutationSchema; + +export const ZUploadDocumentSuccessfulSchema = z.object({ + url: z.string(), + key: z.string(), +}); + +export type TUploadDocumentSuccessfulSchema = z.infer; + +export const ZCreateDocumentMutationSchema = z.object({ + title: z.string().min(1), + recipients: z.array( + z.object({ + name: z.string().min(1), + email: z.string().email().min(1), + role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), + }), + ), + meta: z + .object({ + subject: z.string(), + message: z.string(), + timezone: z.string(), + dateFormat: z.string(), + redirectUrl: z.string(), + }) + .partial(), +}); + +export type TCreateDocumentMutationSchema = z.infer; + +export const ZCreateDocumentMutationResponseSchema = z.object({ + uploadUrl: z.string().min(1), + documentId: z.number(), + recipients: z.array( + z.object({ + recipientId: z.number(), + token: z.string(), + role: z.nativeEnum(RecipientRole), + }), + ), +}); + +export type TCreateDocumentMutationResponseSchema = z.infer< + typeof ZCreateDocumentMutationResponseSchema +>; + +export const ZCreateDocumentFromTemplateMutationSchema = z.object({ + title: z.string().min(1), + recipients: z.array( + z.object({ + name: z.string().min(1), + email: z.string().email().min(1), + role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), + }), + ), + meta: z + .object({ + subject: z.string(), + message: z.string(), + timezone: z.string(), + dateFormat: z.string(), + redirectUrl: z.string(), + }) + .partial() + .optional(), +}); + +export type TCreateDocumentFromTemplateMutationSchema = z.infer< + typeof ZCreateDocumentFromTemplateMutationSchema +>; + +export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({ + documentId: z.number(), + recipients: z.array( + z.object({ + recipientId: z.number(), + name: z.string(), + email: z.string().email().min(1), + token: z.string(), + role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), + }), + ), +}); + +export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer< + typeof ZCreateDocumentFromTemplateMutationResponseSchema +>; + +export const ZCreateRecipientMutationSchema = z.object({ + name: z.string().min(1), + email: z.string().email().min(1), + role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), +}); + +/** + * Recipients + */ +export type TCreateRecipientMutationSchema = z.infer; + +export const ZUpdateRecipientMutationSchema = ZCreateRecipientMutationSchema.partial(); + +export type TUpdateRecipientMutationSchema = z.infer; + +export const ZDeleteRecipientMutationSchema = null; + +export type TDeleteRecipientMutationSchema = typeof ZDeleteRecipientMutationSchema; + +export const ZSuccessfulRecipientResponseSchema = z.object({ + id: z.number(), + // !: This handles the fact that we have null documentId's for templates + // !: while we won't need the default we must add it to satisfy typescript + documentId: z.number().nullish().default(-1), + email: z.string().email().min(1), + name: z.string(), + role: z.nativeEnum(RecipientRole), + token: z.string(), + // !: Not used for now + // expired: z.string(), + signedAt: z.date().nullable(), + readStatus: z.nativeEnum(ReadStatus), + signingStatus: z.nativeEnum(SigningStatus), + sendStatus: z.nativeEnum(SendStatus), +}); + +export type TSuccessfulRecipientResponseSchema = z.infer; + +/** + * Fields + */ +export const ZCreateFieldMutationSchema = z.object({ + recipientId: z.number(), + type: z.nativeEnum(FieldType), + pageNumber: z.number(), + pageX: z.number(), + pageY: z.number(), + pageWidth: z.number(), + pageHeight: z.number(), +}); + +export type TCreateFieldMutationSchema = z.infer; + +export const ZUpdateFieldMutationSchema = ZCreateFieldMutationSchema.partial(); + +export type TUpdateFieldMutationSchema = z.infer; + +export const ZDeleteFieldMutationSchema = null; + +export type TDeleteFieldMutationSchema = typeof ZDeleteFieldMutationSchema; + +export const ZSuccessfulFieldResponseSchema = z.object({ + id: z.number(), + documentId: z.number(), + recipientId: z.number(), + type: z.nativeEnum(FieldType), + pageNumber: z.number(), + pageX: z.number(), + pageY: z.number(), + pageWidth: z.number(), + pageHeight: z.number(), + customText: z.string(), + inserted: z.boolean(), +}); + +export type TSuccessfulFieldResponseSchema = z.infer; + +export const ZSuccessfulResponseSchema = z.object({ + documents: ZSuccessfulDocumentResponseSchema.array(), + totalPages: z.number(), +}); + +export type TSuccessfulResponseSchema = z.infer; + +export const ZSuccessfulSigningResponseSchema = z.object({ + message: z.string(), +}); + +export type TSuccessfulSigningResponseSchema = z.infer; + +/** + * General + */ +export const ZAuthorizationHeadersSchema = z.object({ + authorization: z.string(), +}); + +export type TAuthorizationHeadersSchema = z.infer; + +export const ZUnsuccessfulResponseSchema = z.object({ + message: z.string(), +}); + +export type TUnsuccessfulResponseSchema = z.infer; diff --git a/packages/lib/constants/time.ts b/packages/lib/constants/time.ts index e2581e14c..9b525b4b0 100644 --- a/packages/lib/constants/time.ts +++ b/packages/lib/constants/time.ts @@ -1,5 +1,11 @@ +import { Duration } from 'luxon'; + export const ONE_SECOND = 1000; export const ONE_MINUTE = ONE_SECOND * 60; export const ONE_HOUR = ONE_MINUTE * 60; export const ONE_DAY = ONE_HOUR * 24; export const ONE_WEEK = ONE_DAY * 7; +export const ONE_MONTH = Duration.fromObject({ months: 1 }); +export const THREE_MONTHS = Duration.fromObject({ months: 3 }); +export const SIX_MONTHS = Duration.fromObject({ months: 6 }); +export const ONE_YEAR = Duration.fromObject({ years: 1 }); diff --git a/packages/lib/server-only/auth/hash.ts b/packages/lib/server-only/auth/hash.ts index df9931c97..bb0b760fe 100644 --- a/packages/lib/server-only/auth/hash.ts +++ b/packages/lib/server-only/auth/hash.ts @@ -1,4 +1,5 @@ import { compareSync as bcryptCompareSync, hashSync as bcryptHashSync } from 'bcrypt'; +import crypto from 'crypto'; import { SALT_ROUNDS } from '../../constants/auth'; @@ -12,3 +13,7 @@ export const hashSync = (password: string) => { export const compareSync = (password: string, hash: string) => { return bcryptCompareSync(password, hash); }; + +export const hashString = (input: string) => { + return crypto.createHash('sha512').update(input).digest('hex'); +}; diff --git a/packages/lib/server-only/field/create-field.ts b/packages/lib/server-only/field/create-field.ts new file mode 100644 index 000000000..c61e36340 --- /dev/null +++ b/packages/lib/server-only/field/create-field.ts @@ -0,0 +1,41 @@ +import { prisma } from '@documenso/prisma'; +import type { FieldType } from '@documenso/prisma/client'; + +export type CreateFieldOptions = { + documentId: number; + recipientId: number; + type: FieldType; + pageNumber: number; + pageX: number; + pageY: number; + pageWidth: number; + pageHeight: number; +}; + +export const createField = async ({ + documentId, + recipientId, + type, + pageNumber, + pageX, + pageY, + pageWidth, + pageHeight, +}: CreateFieldOptions) => { + const field = await prisma.field.create({ + data: { + documentId, + recipientId, + type, + page: pageNumber, + positionX: pageX, + positionY: pageY, + width: pageWidth, + height: pageHeight, + customText: '', + inserted: false, + }, + }); + + return field; +}; diff --git a/packages/lib/server-only/field/delete-field.ts b/packages/lib/server-only/field/delete-field.ts new file mode 100644 index 000000000..d775c84bd --- /dev/null +++ b/packages/lib/server-only/field/delete-field.ts @@ -0,0 +1,17 @@ +import { prisma } from '@documenso/prisma'; + +export type DeleteFieldOptions = { + fieldId: number; + documentId: number; +}; + +export const deleteField = async ({ fieldId, documentId }: DeleteFieldOptions) => { + const field = await prisma.field.delete({ + where: { + id: fieldId, + documentId, + }, + }); + + return field; +}; diff --git a/packages/lib/server-only/field/get-field-by-id.ts b/packages/lib/server-only/field/get-field-by-id.ts new file mode 100644 index 000000000..0e0f9b2dd --- /dev/null +++ b/packages/lib/server-only/field/get-field-by-id.ts @@ -0,0 +1,17 @@ +import { prisma } from '@documenso/prisma'; + +export type GetFieldByIdOptions = { + fieldId: number; + documentId: number; +}; + +export const getFieldById = async ({ fieldId, documentId }: GetFieldByIdOptions) => { + const field = await prisma.field.findFirst({ + where: { + id: fieldId, + documentId, + }, + }); + + return field; +}; diff --git a/packages/lib/server-only/field/update-field.ts b/packages/lib/server-only/field/update-field.ts new file mode 100644 index 000000000..4d949a8cb --- /dev/null +++ b/packages/lib/server-only/field/update-field.ts @@ -0,0 +1,44 @@ +import { prisma } from '@documenso/prisma'; +import type { FieldType } from '@documenso/prisma/client'; + +export type UpdateFieldOptions = { + fieldId: number; + documentId: number; + recipientId?: number; + type?: FieldType; + pageNumber?: number; + pageX?: number; + pageY?: number; + pageWidth?: number; + pageHeight?: number; +}; + +export const updateField = async ({ + fieldId, + documentId, + recipientId, + type, + pageNumber, + pageX, + pageY, + pageWidth, + pageHeight, +}: UpdateFieldOptions) => { + const field = await prisma.field.update({ + where: { + id: fieldId, + documentId, + }, + data: { + recipientId, + type, + page: pageNumber, + positionX: pageX, + positionY: pageY, + width: pageWidth, + height: pageHeight, + }, + }); + + return field; +}; diff --git a/packages/lib/server-only/public-api/create-api-token.ts b/packages/lib/server-only/public-api/create-api-token.ts new file mode 100644 index 000000000..c0177c2ce --- /dev/null +++ b/packages/lib/server-only/public-api/create-api-token.ts @@ -0,0 +1,51 @@ +import type { Duration } from 'luxon'; +import { DateTime } from 'luxon'; + +import { prisma } from '@documenso/prisma'; + +// temporary choice for testing only +import * as timeConstants from '../../constants/time'; +import { alphaid } from '../../universal/id'; +import { hashString } from '../auth/hash'; + +type TimeConstants = typeof timeConstants & { + [key: string]: number | Duration; +}; + +type CreateApiTokenInput = { + userId: number; + tokenName: string; + expirationDate: string | null; +}; + +export const createApiToken = async ({ + userId, + tokenName, + expirationDate, +}: CreateApiTokenInput) => { + const apiToken = `api_${alphaid(16)}`; + + const hashedToken = hashString(apiToken); + + const timeConstantsRecords: TimeConstants = timeConstants; + + const dbToken = await prisma.apiToken.create({ + data: { + token: hashedToken, + name: tokenName, + userId, + expires: expirationDate + ? DateTime.now().plus(timeConstantsRecords[expirationDate]).toJSDate() + : null, + }, + }); + + if (!dbToken) { + throw new Error('Failed to create the API token'); + } + + return { + id: dbToken.id, + token: apiToken, + }; +}; diff --git a/packages/lib/server-only/public-api/delete-api-token-by-id.ts b/packages/lib/server-only/public-api/delete-api-token-by-id.ts new file mode 100644 index 000000000..398288006 --- /dev/null +++ b/packages/lib/server-only/public-api/delete-api-token-by-id.ts @@ -0,0 +1,15 @@ +import { prisma } from '@documenso/prisma'; + +export type DeleteTokenByIdOptions = { + id: number; + userId: number; +}; + +export const deleteTokenById = async ({ id, userId }: DeleteTokenByIdOptions) => { + return await prisma.apiToken.delete({ + where: { + id, + userId, + }, + }); +}; diff --git a/packages/lib/server-only/public-api/get-all-user-tokens.ts b/packages/lib/server-only/public-api/get-all-user-tokens.ts new file mode 100644 index 000000000..1ba31a6cf --- /dev/null +++ b/packages/lib/server-only/public-api/get-all-user-tokens.ts @@ -0,0 +1,23 @@ +import { prisma } from '@documenso/prisma'; + +export type GetUserTokensOptions = { + userId: number; +}; + +export const getUserTokens = async ({ userId }: GetUserTokensOptions) => { + return await prisma.apiToken.findMany({ + where: { + userId, + }, + select: { + id: true, + name: true, + algorithm: true, + createdAt: true, + expires: true, + }, + orderBy: { + createdAt: 'desc', + }, + }); +}; diff --git a/packages/lib/server-only/public-api/get-api-token-by-id.ts b/packages/lib/server-only/public-api/get-api-token-by-id.ts new file mode 100644 index 000000000..8b25717f9 --- /dev/null +++ b/packages/lib/server-only/public-api/get-api-token-by-id.ts @@ -0,0 +1,15 @@ +import { prisma } from '@documenso/prisma'; + +export type GetApiTokenByIdOptions = { + id: number; + userId: number; +}; + +export const getApiTokenById = async ({ id, userId }: GetApiTokenByIdOptions) => { + return await prisma.apiToken.findFirstOrThrow({ + where: { + id, + userId, + }, + }); +}; diff --git a/packages/lib/server-only/public-api/get-user-by-token.ts b/packages/lib/server-only/public-api/get-user-by-token.ts new file mode 100644 index 000000000..5fe50f336 --- /dev/null +++ b/packages/lib/server-only/public-api/get-user-by-token.ts @@ -0,0 +1,37 @@ +import { prisma } from '@documenso/prisma'; + +import { hashString } from '../auth/hash'; + +export const getUserByApiToken = async ({ token }: { token: string }) => { + const hashedToken = hashString(token); + + const user = await prisma.user.findFirst({ + where: { + ApiToken: { + some: { + token: hashedToken, + }, + }, + }, + include: { + ApiToken: true, + }, + }); + + if (!user) { + throw new Error('Invalid token'); + } + + const retrievedToken = user.ApiToken.find((apiToken) => apiToken.token === hashedToken); + + // This should be impossible but we need to satisfy TypeScript + if (!retrievedToken) { + throw new Error('Invalid token'); + } + + if (retrievedToken.expires && retrievedToken.expires < new Date()) { + throw new Error('Expired token'); + } + + return user; +}; diff --git a/packages/lib/server-only/recipient/delete-recipient.ts b/packages/lib/server-only/recipient/delete-recipient.ts new file mode 100644 index 000000000..67b948f6a --- /dev/null +++ b/packages/lib/server-only/recipient/delete-recipient.ts @@ -0,0 +1,32 @@ +import { prisma } from '@documenso/prisma'; +import { SendStatus } from '@documenso/prisma/client'; + +export type DeleteRecipientOptions = { + documentId: number; + recipientId: number; +}; + +export const deleteRecipient = async ({ documentId, recipientId }: DeleteRecipientOptions) => { + const recipient = await prisma.recipient.findFirst({ + where: { + id: recipientId, + documentId, + }, + }); + + if (!recipient) { + throw new Error('Recipient not found'); + } + + if (recipient.sendStatus !== SendStatus.NOT_SENT) { + throw new Error('Can not delete a recipient that has already been sent a document'); + } + + const deletedRecipient = await prisma.recipient.delete({ + where: { + id: recipient.id, + }, + }); + + return deletedRecipient; +}; diff --git a/packages/lib/server-only/recipient/get-recipient-by-email.ts b/packages/lib/server-only/recipient/get-recipient-by-email.ts new file mode 100644 index 000000000..349149105 --- /dev/null +++ b/packages/lib/server-only/recipient/get-recipient-by-email.ts @@ -0,0 +1,21 @@ +import { prisma } from '@documenso/prisma'; + +export type GetRecipientByEmailOptions = { + documentId: number; + email: string; +}; + +export const getRecipientByEmail = async ({ documentId, email }: GetRecipientByEmailOptions) => { + const recipient = await prisma.recipient.findFirst({ + where: { + documentId, + email: email.toLowerCase(), + }, + }); + + if (!recipient) { + throw new Error('Recipient not found'); + } + + return recipient; +}; diff --git a/packages/lib/server-only/recipient/get-recipient-by-id.ts b/packages/lib/server-only/recipient/get-recipient-by-id.ts new file mode 100644 index 000000000..0db306b80 --- /dev/null +++ b/packages/lib/server-only/recipient/get-recipient-by-id.ts @@ -0,0 +1,21 @@ +import { prisma } from '@documenso/prisma'; + +export type GetRecipientByIdOptions = { + id: number; + documentId: number; +}; + +export const getRecipientById = async ({ documentId, id }: GetRecipientByIdOptions) => { + const recipient = await prisma.recipient.findFirst({ + where: { + documentId, + id, + }, + }); + + if (!recipient) { + throw new Error('Recipient not found'); + } + + return recipient; +}; diff --git a/packages/lib/server-only/recipient/update-recipient.ts b/packages/lib/server-only/recipient/update-recipient.ts new file mode 100644 index 000000000..e1d28ca13 --- /dev/null +++ b/packages/lib/server-only/recipient/update-recipient.ts @@ -0,0 +1,42 @@ +import { prisma } from '@documenso/prisma'; +import type { RecipientRole } from '@documenso/prisma/client'; + +export type UpdateRecipientOptions = { + documentId: number; + recipientId: number; + email?: string; + name?: string; + role?: RecipientRole; +}; + +export const updateRecipient = async ({ + documentId, + recipientId, + email, + name, + role, +}: UpdateRecipientOptions) => { + const recipient = await prisma.recipient.findFirst({ + where: { + id: recipientId, + documentId, + }, + }); + + if (!recipient) { + throw new Error('Recipient not found'); + } + + const updatedRecipient = await prisma.recipient.update({ + where: { + id: recipient.id, + }, + data: { + email: email?.toLowerCase() ?? recipient.email, + name: name ?? recipient.name, + role: role ?? recipient.role, + }, + }); + + return updatedRecipient; +}; diff --git a/packages/lib/server-only/template/create-document-from-template.ts b/packages/lib/server-only/template/create-document-from-template.ts index c520d4ce1..6828bd23f 100644 --- a/packages/lib/server-only/template/create-document-from-template.ts +++ b/packages/lib/server-only/template/create-document-from-template.ts @@ -1,14 +1,21 @@ import { nanoid } from '@documenso/lib/universal/id'; import { prisma } from '@documenso/prisma'; -import type { TCreateDocumentFromTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema'; +import type { RecipientRole } from '@documenso/prisma/client'; -export type CreateDocumentFromTemplateOptions = TCreateDocumentFromTemplateMutationSchema & { +export type CreateDocumentFromTemplateOptions = { + templateId: number; userId: number; + recipients?: { + name?: string; + email: string; + role?: RecipientRole; + }[]; }; export const createDocumentFromTemplate = async ({ templateId, userId, + recipients, }: CreateDocumentFromTemplateOptions) => { const template = await prisma.template.findUnique({ where: { @@ -63,7 +70,11 @@ export const createDocumentFromTemplate = async ({ }, include: { - Recipient: true, + Recipient: { + orderBy: { + id: 'asc', + }, + }, }, }); @@ -88,5 +99,34 @@ export const createDocumentFromTemplate = async ({ }), }); + if (recipients && recipients.length > 0) { + document.Recipient = await Promise.all( + recipients.map(async (recipient, index) => { + const existingRecipient = document.Recipient.at(index); + + return await prisma.recipient.upsert({ + where: { + documentId_email: { + documentId: document.id, + email: existingRecipient?.email ?? recipient.email, + }, + }, + update: { + name: recipient.name, + email: recipient.email, + role: recipient.role, + }, + create: { + documentId: document.id, + email: recipient.email, + name: recipient.name, + role: recipient.role, + token: nanoid(), + }, + }); + }), + ); + } + return document; }; diff --git a/packages/prisma/migrations/20231123132053_public_api_api_token/migration.sql b/packages/prisma/migrations/20231123132053_public_api_api_token/migration.sql new file mode 100644 index 000000000..d3c9106c4 --- /dev/null +++ b/packages/prisma/migrations/20231123132053_public_api_api_token/migration.sql @@ -0,0 +1,21 @@ +-- CreateEnum +CREATE TYPE "ApiTokenAlgorithm" AS ENUM ('SHA512'); + +-- CreateTable +CREATE TABLE "ApiToken" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "token" TEXT NOT NULL, + "algorithm" "ApiTokenAlgorithm" NOT NULL DEFAULT 'SHA512', + "expires" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" INTEGER NOT NULL, + + CONSTRAINT "ApiToken_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiToken_token_key" ON "ApiToken"("token"); + +-- AddForeignKey +ALTER TABLE "ApiToken" ADD CONSTRAINT "ApiToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/prisma/migrations/20231220124343_add_cascade_delete_user_apitoken/migration.sql b/packages/prisma/migrations/20231220124343_add_cascade_delete_user_apitoken/migration.sql new file mode 100644 index 000000000..4eb0b4760 --- /dev/null +++ b/packages/prisma/migrations/20231220124343_add_cascade_delete_user_apitoken/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "ApiToken" DROP CONSTRAINT "ApiToken_userId_fkey"; + +-- AddForeignKey +ALTER TABLE "ApiToken" ADD CONSTRAINT "ApiToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/migrations/20240208135802_make_expiry_date_optional_api_tokens/migration.sql b/packages/prisma/migrations/20240208135802_make_expiry_date_optional_api_tokens/migration.sql new file mode 100644 index 000000000..02e291481 --- /dev/null +++ b/packages/prisma/migrations/20240208135802_make_expiry_date_optional_api_tokens/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ApiToken" ALTER COLUMN "expires" DROP NOT NULL; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 2887cd6d2..0712beaf5 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -43,9 +43,9 @@ model User { twoFactorSecret String? twoFactorEnabled Boolean @default(false) twoFactorBackupCodes String? - - VerificationToken VerificationToken[] - Template Template[] + VerificationToken VerificationToken[] + ApiToken ApiToken[] + Template Template[] securityAuditLogs UserSecurityAuditLog[] @@index([email]) @@ -94,6 +94,21 @@ model VerificationToken { user User @relation(fields: [userId], references: [id], onDelete: Cascade) } +enum ApiTokenAlgorithm { + SHA512 +} + +model ApiToken { + id Int @id @default(autoincrement()) + name String + token String @unique + algorithm ApiTokenAlgorithm @default(SHA512) + expires DateTime? + createdAt DateTime @default(now()) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + enum SubscriptionStatus { ACTIVE PAST_DUE diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 54c1d5917..fb32bcdf3 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -17,6 +17,8 @@ "@trpc/next": "^10.36.0", "@trpc/react-query": "^10.36.0", "@trpc/server": "^10.36.0", + "@ts-rest/core": "^3.30.5", + "@ts-rest/next": "^3.30.5", "luxon": "^3.4.0", "superjson": "^1.13.1", "ts-pattern": "^5.0.5", diff --git a/packages/trpc/server/api-token-router/router.ts b/packages/trpc/server/api-token-router/router.ts new file mode 100644 index 000000000..596ee8c07 --- /dev/null +++ b/packages/trpc/server/api-token-router/router.ts @@ -0,0 +1,81 @@ +import { TRPCError } from '@trpc/server'; + +import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token'; +import { deleteTokenById } from '@documenso/lib/server-only/public-api/delete-api-token-by-id'; +import { getUserTokens } from '@documenso/lib/server-only/public-api/get-all-user-tokens'; +import { getApiTokenById } from '@documenso/lib/server-only/public-api/get-api-token-by-id'; + +import { authenticatedProcedure, router } from '../trpc'; +import { + ZCreateTokenMutationSchema, + ZDeleteTokenByIdMutationSchema, + ZGetApiTokenByIdQuerySchema, +} from './schema'; + +export const apiTokenRouter = router({ + getTokens: authenticatedProcedure.query(async ({ ctx }) => { + try { + return await getUserTokens({ userId: ctx.user.id }); + } catch (e) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'We were unable to find your API tokens. Please try again.', + }); + } + }), + + getTokenById: authenticatedProcedure + .input(ZGetApiTokenByIdQuerySchema) + .query(async ({ input, ctx }) => { + try { + const { id } = input; + + return await getApiTokenById({ + id, + userId: ctx.user.id, + }); + } catch (e) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'We were unable to find this API token. Please try again.', + }); + } + }), + + createToken: authenticatedProcedure + .input(ZCreateTokenMutationSchema) + .mutation(async ({ input, ctx }) => { + try { + const { tokenName, expirationDate } = input; + + return await createApiToken({ + userId: ctx.user.id, + tokenName, + expirationDate, + }); + } catch (e) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'We were unable to create an API token. Please try again.', + }); + } + }), + + deleteTokenById: authenticatedProcedure + .input(ZDeleteTokenByIdMutationSchema) + .mutation(async ({ input, ctx }) => { + try { + const { id } = input; + + return await deleteTokenById({ + id, + userId: ctx.user.id, + }); + } catch (e) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'We were unable to delete this API Token. Please try again.', + }); + } + }), +}); diff --git a/packages/trpc/server/api-token-router/schema.ts b/packages/trpc/server/api-token-router/schema.ts new file mode 100644 index 000000000..e4aa6c0f8 --- /dev/null +++ b/packages/trpc/server/api-token-router/schema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +export const ZGetApiTokenByIdQuerySchema = z.object({ + id: z.number().min(1), +}); + +export type TGetApiTokenByIdQuerySchema = z.infer; + +export const ZCreateTokenMutationSchema = z.object({ + tokenName: z.string().min(3, { message: 'The token name should be 3 characters or longer' }), + expirationDate: z.string().nullable(), +}); + +export type TCreateTokenMutationSchema = z.infer; + +export const ZDeleteTokenByIdMutationSchema = z.object({ + id: z.number().min(1), +}); + +export type TDeleteTokenByIdMutationSchema = z.infer; diff --git a/packages/trpc/server/router.ts b/packages/trpc/server/router.ts index aec70fd63..72fe0b2be 100644 --- a/packages/trpc/server/router.ts +++ b/packages/trpc/server/router.ts @@ -1,4 +1,5 @@ import { adminRouter } from './admin-router/router'; +import { apiTokenRouter } from './api-token-router/router'; import { authRouter } from './auth-router/router'; import { cryptoRouter } from './crypto/router'; import { documentRouter } from './document-router/router'; @@ -21,6 +22,7 @@ export const appRouter = router({ recipient: recipientRouter, admin: adminRouter, shareLink: shareLinkRouter, + apiToken: apiTokenRouter, singleplayer: singleplayerRouter, team: teamRouter, template: templateRouter, diff --git a/packages/trpc/tsconfig.json b/packages/trpc/tsconfig.json index 4aefcb98c..dc21318a7 100644 --- a/packages/trpc/tsconfig.json +++ b/packages/trpc/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "@documenso/tsconfig/react-library.json", "include": ["."], - "exclude": ["dist", "build", "node_modules"] + "exclude": ["dist", "build", "node_modules"], + "compilerOptions": { + "strict": true, + } }