mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 20:42:34 +10:00
Compare commits
2 Commits
experiment
...
wip/rr7-be
| Author | SHA1 | Date | |
|---|---|---|---|
| 071ce70292 | |||
| 866b036484 |
4
apps/remix/.dockerignore
Normal file
4
apps/remix/.dockerignore
Normal file
@ -0,0 +1,4 @@
|
||||
.react-router
|
||||
build
|
||||
node_modules
|
||||
README.md
|
||||
9
apps/remix/.gitignore
vendored
Normal file
9
apps/remix/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
/node_modules/
|
||||
|
||||
# React Router
|
||||
/.react-router/
|
||||
/build/
|
||||
|
||||
# Vite
|
||||
vite.config.ts.timestamp*
|
||||
22
apps/remix/Dockerfile
Normal file
22
apps/remix/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
FROM node:20-alpine AS development-dependencies-env
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN npm ci
|
||||
|
||||
FROM node:20-alpine AS production-dependencies-env
|
||||
COPY ./package.json package-lock.json /app/
|
||||
WORKDIR /app
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
FROM node:20-alpine AS build-env
|
||||
COPY . /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
COPY ./package.json package-lock.json /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["npm", "run", "start"]
|
||||
25
apps/remix/Dockerfile.bun
Normal file
25
apps/remix/Dockerfile.bun
Normal file
@ -0,0 +1,25 @@
|
||||
FROM oven/bun:1 AS dependencies-env
|
||||
COPY . /app
|
||||
|
||||
FROM dependencies-env AS development-dependencies-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
WORKDIR /app
|
||||
RUN bun i --frozen-lockfile
|
||||
|
||||
FROM dependencies-env AS production-dependencies-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
WORKDIR /app
|
||||
RUN bun i --production
|
||||
|
||||
FROM dependencies-env AS build-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN bun run build
|
||||
|
||||
FROM dependencies-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["bun", "run", "start"]
|
||||
26
apps/remix/Dockerfile.pnpm
Normal file
26
apps/remix/Dockerfile.pnpm
Normal file
@ -0,0 +1,26 @@
|
||||
FROM node:20-alpine AS dependencies-env
|
||||
RUN npm i -g pnpm
|
||||
COPY . /app
|
||||
|
||||
FROM dependencies-env AS development-dependencies-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
WORKDIR /app
|
||||
RUN pnpm i --frozen-lockfile
|
||||
|
||||
FROM dependencies-env AS production-dependencies-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
WORKDIR /app
|
||||
RUN pnpm i --prod --frozen-lockfile
|
||||
|
||||
FROM dependencies-env AS build-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN pnpm build
|
||||
|
||||
FROM dependencies-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["pnpm", "start"]
|
||||
100
apps/remix/README.md
Normal file
100
apps/remix/README.md
Normal file
@ -0,0 +1,100 @@
|
||||
# Welcome to React Router!
|
||||
|
||||
A modern, production-ready template for building full-stack React applications using React Router.
|
||||
|
||||
[](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 Server-side rendering
|
||||
- ⚡️ Hot Module Replacement (HMR)
|
||||
- 📦 Asset bundling and optimization
|
||||
- 🔄 Data loading and mutations
|
||||
- 🔒 TypeScript by default
|
||||
- 🎉 TailwindCSS for styling
|
||||
- 📖 [React Router docs](https://reactrouter.com/)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
Start the development server with HMR:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Your application will be available at `http://localhost:5173`.
|
||||
|
||||
## Building for Production
|
||||
|
||||
Create a production build:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
This template includes three Dockerfiles optimized for different package managers:
|
||||
|
||||
- `Dockerfile` - for npm
|
||||
- `Dockerfile.pnpm` - for pnpm
|
||||
- `Dockerfile.bun` - for bun
|
||||
|
||||
To build and run using Docker:
|
||||
|
||||
```bash
|
||||
# For npm
|
||||
docker build -t my-app .
|
||||
|
||||
# For pnpm
|
||||
docker build -f Dockerfile.pnpm -t my-app .
|
||||
|
||||
# For bun
|
||||
docker build -f Dockerfile.bun -t my-app .
|
||||
|
||||
# Run the container
|
||||
docker run -p 3000:3000 my-app
|
||||
```
|
||||
|
||||
The containerized application can be deployed to any platform that supports Docker, including:
|
||||
|
||||
- AWS ECS
|
||||
- Google Cloud Run
|
||||
- Azure Container Apps
|
||||
- Digital Ocean App Platform
|
||||
- Fly.io
|
||||
- Railway
|
||||
|
||||
### DIY Deployment
|
||||
|
||||
If you're familiar with deploying Node applications, the built-in app server is production-ready.
|
||||
|
||||
Make sure to deploy the output of `npm run build`
|
||||
|
||||
```
|
||||
├── package.json
|
||||
├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)
|
||||
├── build/
|
||||
│ ├── client/ # Static assets
|
||||
│ └── server/ # Server-side code
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ using React Router.
|
||||
1
apps/remix/app/app.css
Normal file
1
apps/remix/app/app.css
Normal file
@ -0,0 +1 @@
|
||||
@import '@documenso/ui/styles/theme.css';
|
||||
13
apps/remix/app/lib/auth-client.ts
Normal file
13
apps/remix/app/lib/auth-client.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { twoFactor } from 'better-auth/plugins';
|
||||
import { createAuthClient } from 'better-auth/react';
|
||||
|
||||
import { passkeyClientPlugin } from './auth/passkey-plugin/client';
|
||||
|
||||
// make sure to import from better-auth/react
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: 'http://localhost:3000',
|
||||
plugins: [twoFactor(), passkeyClientPlugin()],
|
||||
});
|
||||
|
||||
export const { signIn, signOut, useSession } = authClient;
|
||||
112
apps/remix/app/lib/auth.server.ts
Normal file
112
apps/remix/app/lib/auth.server.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { compare, hash } from '@node-rs/bcrypt';
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
||||
import { twoFactor } from 'better-auth/plugins';
|
||||
|
||||
import { getAuthenticatorOptions } from '@documenso/lib/utils/authenticator';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { passkeyPlugin } from './auth/passkey-plugin';
|
||||
|
||||
// todo: import from @documenso/lib/constants/auth
|
||||
export const SALT_ROUNDS = 12;
|
||||
|
||||
const passkeyOptions = getAuthenticatorOptions();
|
||||
|
||||
export const auth = betterAuth({
|
||||
appName: 'Documenso',
|
||||
plugins: [
|
||||
twoFactor({
|
||||
issuer: 'Documenso',
|
||||
skipVerificationOnEnable: true,
|
||||
// totpOptions: {
|
||||
|
||||
// },
|
||||
schema: {
|
||||
twoFactor: {
|
||||
modelName: 'TwoFactor',
|
||||
fields: {
|
||||
userId: 'userId',
|
||||
secret: 'secret',
|
||||
backupCodes: 'backupCodes',
|
||||
},
|
||||
},
|
||||
},
|
||||
// todo: add options
|
||||
}),
|
||||
passkeyPlugin(),
|
||||
// passkey({
|
||||
// rpID: passkeyOptions.rpId,
|
||||
// rpName: passkeyOptions.rpName,
|
||||
// origin: passkeyOptions.origin,
|
||||
// schema: {
|
||||
// passkey: {
|
||||
// fields: {
|
||||
// publicKey: 'credentialPublicKey',
|
||||
// credentialID: 'credentialId',
|
||||
// deviceType: 'credentialDeviceType',
|
||||
// backedUp: 'credentialBackedUp',
|
||||
// // transports: '',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
],
|
||||
secret: 'secret', // todo
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: 'postgresql',
|
||||
}),
|
||||
databaseHooks: {
|
||||
account: {
|
||||
create: {
|
||||
before: (session) => {
|
||||
return {
|
||||
data: {
|
||||
...session,
|
||||
accountId: session.accountId.toString(),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
session: {
|
||||
fields: {
|
||||
token: 'sessionToken',
|
||||
expiresAt: 'expires',
|
||||
},
|
||||
},
|
||||
user: {
|
||||
fields: {
|
||||
emailVerified: 'isEmailVerified',
|
||||
},
|
||||
},
|
||||
account: {
|
||||
fields: {
|
||||
providerId: 'provider',
|
||||
accountId: 'providerAccountId',
|
||||
refreshToken: 'refresh_token',
|
||||
accessToken: 'access_token',
|
||||
idToken: 'id_token',
|
||||
},
|
||||
},
|
||||
advanced: {
|
||||
generateId: false,
|
||||
},
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
},
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
requireEmailVerification: false,
|
||||
// maxPasswordLength: 128,
|
||||
// minPasswordLength: 8,
|
||||
password: {
|
||||
hash: async (password) => hash(password, SALT_ROUNDS),
|
||||
verify: async ({ hash, password }) => compare(password, hash),
|
||||
},
|
||||
},
|
||||
});
|
||||
24
apps/remix/app/lib/auth/passkey-plugin/client.ts
Normal file
24
apps/remix/app/lib/auth/passkey-plugin/client.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { BetterAuthClientPlugin } from 'better-auth';
|
||||
|
||||
import type { passkeyPlugin } from './index';
|
||||
|
||||
type PasskeyPlugin = typeof passkeyPlugin;
|
||||
|
||||
export const passkeyClientPlugin = () => {
|
||||
const passkeySignin = () => {
|
||||
//
|
||||
// credential: JSON.stringify(credential),
|
||||
// callbackUrl,
|
||||
};
|
||||
|
||||
return {
|
||||
id: 'passkeyPlugin',
|
||||
getActions: () => ({
|
||||
signIn: {
|
||||
passkey: () => passkeySignin,
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
$InferServerPlugin: {} as ReturnType<PasskeyPlugin>,
|
||||
} satisfies BetterAuthClientPlugin;
|
||||
};
|
||||
165
apps/remix/app/lib/auth/passkey-plugin/index.ts
Normal file
165
apps/remix/app/lib/auth/passkey-plugin/index.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import type { BetterAuthPlugin } from 'better-auth';
|
||||
import { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins';
|
||||
|
||||
export const passkeyPlugin = () =>
|
||||
({
|
||||
id: 'passkeyPlugin',
|
||||
schema: {
|
||||
user: {
|
||||
fields: {
|
||||
// twoFactorEnabled: {
|
||||
// type: 'boolean',
|
||||
// required: false,
|
||||
// },
|
||||
// twoFactorBackupCodes: {
|
||||
// type: 'string',
|
||||
// required: false,
|
||||
// },
|
||||
// twoFactorSecret: {
|
||||
// type: 'string',
|
||||
// required: false,
|
||||
// },
|
||||
// birthday: {
|
||||
// type: 'date', // string, number, boolean, date
|
||||
// required: true, // if the field should be required on a new record. (default: false)
|
||||
// unique: false, // if the field should be unique. (default: false)
|
||||
// reference: null, // if the field is a reference to another table. (default: null)
|
||||
// },
|
||||
},
|
||||
},
|
||||
},
|
||||
endpoints: {
|
||||
authorize: createAuthEndpoint(
|
||||
'/passkey/authorize',
|
||||
{
|
||||
method: 'POST',
|
||||
// use: [],
|
||||
},
|
||||
async (ctx) => {
|
||||
const csrfToken = credentials?.csrfToken;
|
||||
|
||||
if (typeof csrfToken !== 'string' || csrfToken.length === 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
let requestBodyCrediential: TAuthenticationResponseJSONSchema | null = null;
|
||||
|
||||
try {
|
||||
const parsedBodyCredential = JSON.parse(req.body?.credential);
|
||||
requestBodyCrediential = ZAuthenticationResponseJSONSchema.parse(parsedBodyCredential);
|
||||
} catch {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
const challengeToken = await prisma.anonymousVerificationToken
|
||||
.delete({
|
||||
where: {
|
||||
id: csrfToken,
|
||||
},
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
if (!challengeToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (challengeToken.expiresAt < new Date()) {
|
||||
throw new AppError(AppErrorCode.EXPIRED_CODE);
|
||||
}
|
||||
|
||||
const passkey = await prisma.passkey.findFirst({
|
||||
where: {
|
||||
credentialId: Buffer.from(requestBodyCrediential.id, 'base64'),
|
||||
},
|
||||
include: {
|
||||
User: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!passkey) {
|
||||
throw new AppError(AppErrorCode.NOT_SETUP);
|
||||
}
|
||||
|
||||
const user = passkey.User;
|
||||
|
||||
const { rpId, origin } = getAuthenticatorOptions();
|
||||
|
||||
const verification = await verifyAuthenticationResponse({
|
||||
response: requestBodyCrediential,
|
||||
expectedChallenge: challengeToken.token,
|
||||
expectedOrigin: origin,
|
||||
expectedRPID: rpId,
|
||||
authenticator: {
|
||||
credentialID: new Uint8Array(Array.from(passkey.credentialId)),
|
||||
credentialPublicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
counter: Number(passkey.counter),
|
||||
},
|
||||
}).catch(() => null);
|
||||
|
||||
const requestMetadata = extractNextAuthRequestMetadata(req);
|
||||
|
||||
if (!verification?.verified) {
|
||||
await prisma.userSecurityAuditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
ipAddress: requestMetadata.ipAddress,
|
||||
userAgent: requestMetadata.userAgent,
|
||||
type: UserSecurityAuditLogType.SIGN_IN_PASSKEY_FAIL,
|
||||
},
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
await prisma.passkey.update({
|
||||
where: {
|
||||
id: passkey.id,
|
||||
},
|
||||
data: {
|
||||
lastUsedAt: new Date(),
|
||||
counter: verification.authenticationInfo.newCounter,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: Number(user.id),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified?.toISOString() ?? null,
|
||||
} satisfies User;
|
||||
},
|
||||
),
|
||||
},
|
||||
hooks: {
|
||||
before: [
|
||||
{
|
||||
matcher: (context) => context.path.startsWith('/sign-in/email'),
|
||||
handler: createAuthMiddleware(async (ctx) => {
|
||||
console.log('here...');
|
||||
|
||||
const { birthday } = ctx.body;
|
||||
|
||||
if ((!birthday) instanceof Date) {
|
||||
throw new APIError('BAD_REQUEST', { message: 'Birthday must be of type Date.' });
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5));
|
||||
|
||||
if (birthday >= fiveYearsAgo) {
|
||||
throw new APIError('BAD_REQUEST', { message: 'User must be above 5 years old.' });
|
||||
}
|
||||
|
||||
return { context: ctx };
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}) satisfies BetterAuthPlugin;
|
||||
74
apps/remix/app/root.tsx
Normal file
74
apps/remix/app/root.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
Links,
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
isRouteErrorResponse,
|
||||
} from 'react-router';
|
||||
|
||||
import type { Route } from './+types/root';
|
||||
import stylesheet from './app.css?url';
|
||||
|
||||
export const links: Route.LinksFunction = () => [
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{
|
||||
rel: 'preconnect',
|
||||
href: 'https://fonts.gstatic.com',
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
|
||||
},
|
||||
{ rel: 'stylesheet', href: stylesheet },
|
||||
];
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
let message = 'Oops!';
|
||||
let details = 'An unexpected error occurred.';
|
||||
let stack: string | undefined;
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
message = error.status === 404 ? '404' : 'Error';
|
||||
details =
|
||||
error.status === 404 ? 'The requested page could not be found.' : error.statusText || details;
|
||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
||||
details = error.message;
|
||||
stack = error.stack;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="container mx-auto p-4 pt-16">
|
||||
<h1>{message}</h1>
|
||||
<p>{details}</p>
|
||||
{stack && (
|
||||
<pre className="w-full overflow-x-auto p-4">
|
||||
<code>{stack}</code>
|
||||
</pre>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
4
apps/remix/app/routes.ts
Normal file
4
apps/remix/app/routes.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { type RouteConfig } from '@react-router/dev/routes';
|
||||
import { flatRoutes } from '@react-router/fs-routes';
|
||||
|
||||
export default flatRoutes() satisfies RouteConfig;
|
||||
193
apps/remix/app/routes/_index.tsx
Normal file
193
apps/remix/app/routes/_index.tsx
Normal file
@ -0,0 +1,193 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import { authClient, signOut, useSession } from '~/lib/auth-client';
|
||||
import { auth } from '~/lib/auth.server';
|
||||
|
||||
import type { Route } from '../+types/root';
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: 'New React Router App' },
|
||||
{ name: 'description', content: 'Welcome to React Router!' },
|
||||
];
|
||||
}
|
||||
|
||||
export async function loader({ params, request, context }: Route.LoaderArgs) {
|
||||
const session = await auth.api.getSession({
|
||||
query: {
|
||||
disableCookieCache: true,
|
||||
},
|
||||
headers: request.headers, // pass the headers
|
||||
});
|
||||
|
||||
return {
|
||||
session,
|
||||
};
|
||||
}
|
||||
|
||||
export function clientLoader({ params }: Route.ClientLoaderArgs) {
|
||||
return {
|
||||
session: authClient.getSession(),
|
||||
};
|
||||
}
|
||||
|
||||
export default function Home({ loaderData }: Route.ComponentProps) {
|
||||
const { data } = useSession();
|
||||
|
||||
const [email, setEmail] = useState('deepfriedcoconut@gmail.com');
|
||||
const [password, setPassword] = useState('password');
|
||||
|
||||
const signIn = async () => {
|
||||
await authClient.signIn.email(
|
||||
{
|
||||
email,
|
||||
password,
|
||||
},
|
||||
{
|
||||
onRequest: (ctx) => {
|
||||
// show loading state
|
||||
},
|
||||
onSuccess: (ctx) => {
|
||||
console.log('success');
|
||||
// redirect to home
|
||||
},
|
||||
onError: (ctx) => {
|
||||
console.log(ctx.error);
|
||||
alert(ctx.error);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const signUp = async () => {
|
||||
await authClient.signUp.email(
|
||||
{
|
||||
email,
|
||||
password,
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
onRequest: (ctx) => {
|
||||
// show loading state
|
||||
},
|
||||
onSuccess: (ctx) => {
|
||||
console.log(ctx);
|
||||
// redirect to home
|
||||
},
|
||||
onError: (ctx) => {
|
||||
console.log(ctx.error);
|
||||
alert(ctx.error);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-center justify-center pb-4 pt-16">
|
||||
<h1>Status: {data ? 'Authenticated' : 'Not Authenticated'}</h1>
|
||||
|
||||
{data ? (
|
||||
<>
|
||||
<div>
|
||||
<p>Session data</p>
|
||||
<p className="mt-2 max-w-2xl text-xs">{JSON.stringify(data, null, 2)}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
authClient.twoFactor
|
||||
.enable({
|
||||
password: 'password', // user password required
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Enable 2FA
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
authClient.twoFactor.disable({
|
||||
password: 'password',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Disable 2FA
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<button onClick={() => signOut()}>signout</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="">
|
||||
<h2>Sign In</h2>
|
||||
<input
|
||||
className="border border-blue-500"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="border border-blue-500"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<button type="submit" onClick={signIn}>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<h2>Sign Up</h2>
|
||||
|
||||
<input
|
||||
type="email"
|
||||
className="border border-blue-500"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Email"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
className="border border-blue-500"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
<button type="submit" onClick={signUp}>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
authClient.signIn.social({
|
||||
provider: 'google',
|
||||
});
|
||||
}}
|
||||
>
|
||||
google
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={async () => {
|
||||
const response = await authClient.signIn.passkey();
|
||||
console.log(response);
|
||||
}}
|
||||
>
|
||||
passkey
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
12
apps/remix/app/routes/api.auth.$.ts
Normal file
12
apps/remix/app/routes/api.auth.$.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// Adjust the path as necessary
|
||||
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node';
|
||||
|
||||
import { auth } from '~/lib/auth.server';
|
||||
|
||||
export function loader({ request }: LoaderFunctionArgs) {
|
||||
return auth.handler(request);
|
||||
}
|
||||
|
||||
export function action({ request }: ActionFunctionArgs) {
|
||||
return auth.handler(request);
|
||||
}
|
||||
40
apps/remix/app/routes/signin.tsx
Normal file
40
apps/remix/app/routes/signin.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { authClient } from '~/lib/auth-client';
|
||||
|
||||
export default function SignIn() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const signIn = async () => {
|
||||
await authClient.signIn.email(
|
||||
{
|
||||
email,
|
||||
password,
|
||||
},
|
||||
{
|
||||
onRequest: (ctx) => {
|
||||
// show loading state
|
||||
},
|
||||
onSuccess: (ctx) => {
|
||||
// redirect to home
|
||||
},
|
||||
onError: (ctx) => {
|
||||
alert(ctx.error);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Sign In</h2>
|
||||
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
|
||||
<button type="submit" onClick={signIn}>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
apps/remix/package.json
Normal file
43
apps/remix/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@documenso/remix",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production react-router build",
|
||||
"dev": "tsx watch --ignore \"vite.config.ts*\" server/main.ts",
|
||||
"start": "cross-env NODE_ENV=production node dist/server/index.js",
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/api": "*",
|
||||
"@documenso/assets": "*",
|
||||
"@documenso/ee": "*",
|
||||
"@documenso/lib": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@documenso/trpc": "*",
|
||||
"@documenso/ui": "*",
|
||||
"@epic-web/remember": "^1.1.0",
|
||||
"@hono/node-server": "^1.13.7",
|
||||
"@react-router/fs-routes": "^7.1.1",
|
||||
"@react-router/node": "^7.1.1",
|
||||
"@react-router/serve": "^7.1.1",
|
||||
"better-auth": "^1.1.9",
|
||||
"hono": "^4.6.15",
|
||||
"isbot": "^5.1.17",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-router": "^7.1.1",
|
||||
"remix-hono": "^0.0.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.1.1",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"cross-env": "^7.0.3",
|
||||
"tsx": "^4.11.0",
|
||||
"typescript": "5.7.2",
|
||||
"vite": "^5.4.11",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
BIN
apps/remix/public/favicon.ico
Normal file
BIN
apps/remix/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
7
apps/remix/react-router.config.ts
Normal file
7
apps/remix/react-router.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { Config } from '@react-router/dev/config';
|
||||
|
||||
export default {
|
||||
appDirectory: 'app',
|
||||
// Server-side render by default, to enable SPA mode set this to `false`
|
||||
ssr: true,
|
||||
} satisfies Config;
|
||||
45
apps/remix/server/app.ts
Normal file
45
apps/remix/server/app.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { remember } from '@epic-web/remember';
|
||||
import { type HttpBindings } from '@hono/node-server';
|
||||
import { Hono } from 'hono';
|
||||
import { reactRouter } from 'remix-hono/handler';
|
||||
|
||||
type Bindings = HttpBindings;
|
||||
|
||||
const app = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
const viteDevServer = isProduction
|
||||
? undefined
|
||||
: await import('vite').then(async (vite) =>
|
||||
vite.createServer({
|
||||
server: { middlewareMode: true },
|
||||
}),
|
||||
);
|
||||
|
||||
const reactRouterMiddleware = remember('reactRouterMiddleware', async () =>
|
||||
reactRouter({
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
build: isProduction
|
||||
? // @ts-expect-error build/server/index.js is a build artifact
|
||||
await import('../build/server/index.js')
|
||||
: async () => viteDevServer!.ssrLoadModule('virtual:react-router/server-build'),
|
||||
}),
|
||||
);
|
||||
|
||||
// app.get('/', (c) => c.text('Hello, world!'));
|
||||
if (viteDevServer) {
|
||||
app.use('*', async (c, next) => {
|
||||
return new Promise((resolve) => {
|
||||
viteDevServer.middlewares(c.env.incoming, c.env.outgoing, () => resolve(next()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.use('*', async (c, next) => {
|
||||
const middleware = await reactRouterMiddleware;
|
||||
|
||||
return middleware(c, next);
|
||||
});
|
||||
|
||||
export default app;
|
||||
7
apps/remix/server/main.ts
Normal file
7
apps/remix/server/main.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { serve } from '@hono/node-server';
|
||||
|
||||
import app from './app';
|
||||
|
||||
serve(app, (info) => {
|
||||
console.log(`Server is running on http://localhost:${info.port}`);
|
||||
});
|
||||
18
apps/remix/tailwind.config.ts
Normal file
18
apps/remix/tailwind.config.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const baseConfig = require('@documenso/tailwind-config');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
content: [
|
||||
...baseConfig.content,
|
||||
'./app/**/*.{ts,tsx}',
|
||||
`${path.join(require.resolve('@documenso/ui'), '..')}/components/**/*.{ts,tsx}`,
|
||||
`${path.join(require.resolve('@documenso/ui'), '..')}/icons/**/*.{ts,tsx}`,
|
||||
`${path.join(require.resolve('@documenso/ui'), '..')}/lib/**/*.{ts,tsx}`,
|
||||
`${path.join(require.resolve('@documenso/ui'), '..')}/primitives/**/*.{ts,tsx}`,
|
||||
`${path.join(require.resolve('@documenso/email'), '..')}/templates/**/*.{ts,tsx}`,
|
||||
`${path.join(require.resolve('@documenso/email'), '..')}/template-components/**/*.{ts,tsx}`,
|
||||
`${path.join(require.resolve('@documenso/email'), '..')}/providers/**/*.{ts,tsx}`,
|
||||
],
|
||||
};
|
||||
27
apps/remix/tsconfig.json
Normal file
27
apps/remix/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"include": [
|
||||
"**/*",
|
||||
"**/.server/**/*",
|
||||
"**/.client/**/*",
|
||||
".react-router/types/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"types": ["node", "vite/client"],
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./app/*"]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
17
apps/remix/vite.config.ts
Normal file
17
apps/remix/vite.config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { reactRouter } from '@react-router/dev/vite';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [tailwindcss, autoprefixer],
|
||||
},
|
||||
},
|
||||
plugins: [reactRouter(), tsconfigPaths()],
|
||||
optimizeDeps: {
|
||||
exclude: ['@node-rs/bcrypt'],
|
||||
},
|
||||
});
|
||||
@ -73,6 +73,6 @@
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.7.2"
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
||||
import type { Document, Recipient, User } from '@documenso/prisma/client';
|
||||
|
||||
export type DocumentPageViewInformationProps = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
document: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
Recipient: Recipient[];
|
||||
|
||||
@ -16,7 +16,7 @@ import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type DocumentPageViewRecentActivityProps = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const DocumentPageViewRecentActivity = ({
|
||||
|
||||
@ -10,7 +10,7 @@ import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
||||
import type { Template, User } from '@documenso/prisma/client';
|
||||
|
||||
export type TemplatePageViewInformationProps = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
template: Template & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
};
|
||||
|
||||
@ -45,7 +45,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
export type TransferTeamDialogProps = {
|
||||
teamId: number;
|
||||
teamName: string;
|
||||
ownerUserId: string;
|
||||
ownerUserId: number;
|
||||
trigger?: React.ReactNode;
|
||||
};
|
||||
|
||||
@ -98,7 +98,7 @@ export const TransferTeamDialog = ({
|
||||
try {
|
||||
await requestTeamOwnershipTransfer({
|
||||
teamId,
|
||||
newOwnerUserId,
|
||||
newOwnerUserId: Number.parseInt(newOwnerUserId),
|
||||
clearPaymentMethods,
|
||||
});
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ import { UpdateTeamMemberDialog } from '../dialogs/update-team-member-dialog';
|
||||
|
||||
export type TeamMembersDataTableProps = {
|
||||
currentUserTeamRole: TeamMemberRole;
|
||||
teamOwneruserId: string;
|
||||
teamOwnerUserId: number;
|
||||
teamId: number;
|
||||
teamName: string;
|
||||
};
|
||||
|
||||
@ -20,7 +20,7 @@ export type TeamsMemberPageDataTableProps = {
|
||||
currentUserTeamRole: TeamMemberRole;
|
||||
teamId: number;
|
||||
teamName: string;
|
||||
teamOwneruserId: string;
|
||||
teamOwnerUserId: number;
|
||||
};
|
||||
|
||||
export const TeamsMemberPageDataTable = ({
|
||||
|
||||
@ -24,7 +24,7 @@ import { DocumentHistorySheetChanges } from './document-history-sheet-changes';
|
||||
|
||||
export type DocumentHistorySheetProps = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
isMenuOpen?: boolean;
|
||||
onMenuOpenChange?: (_value: boolean) => void;
|
||||
children?: React.ReactNode;
|
||||
|
||||
4498
package-lock.json
generated
4498
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
"build": "turbo run build",
|
||||
"build:web": "turbo run build --filter=@documenso/web",
|
||||
"dev": "turbo run dev --filter=@documenso/web",
|
||||
"dev:remix": "turbo run dev --filter=@documenso/remix",
|
||||
"dev:web": "turbo run dev --filter=@documenso/web",
|
||||
"dev:docs": "turbo run dev --filter=@documenso/documentation",
|
||||
"dev:openpage-api": "turbo run dev --filter=@documenso/openpage-api",
|
||||
@ -70,15 +71,12 @@
|
||||
"luxon": "^3.5.0",
|
||||
"mupdf": "^1.0.0",
|
||||
"next-runtime-env": "^3.2.0",
|
||||
"@prisma/client": "^6.1.0",
|
||||
"prisma": "^6.1.0",
|
||||
"react": "^18",
|
||||
"typescript": "5.7.2",
|
||||
"zod": "3.24.1"
|
||||
},
|
||||
"overrides": {
|
||||
"next": "14.2.6",
|
||||
"@prisma/client": "^6.1.0",
|
||||
"prisma": "^6.1.0",
|
||||
"zod": "3.24.1"
|
||||
},
|
||||
"trigger.dev": {
|
||||
|
||||
@ -91,7 +91,7 @@ export const getStripeCustomerIdByUser = async (user: User) => {
|
||||
return await getStripeCustomerByUser(user).then((session) => session.stripeCustomer.id);
|
||||
};
|
||||
|
||||
const syncStripeCustomerSubscriptions = async (userId: string, stripeCustomerId: string) => {
|
||||
const syncStripeCustomerSubscriptions = async (userId: number, stripeCustomerId: string) => {
|
||||
const stripeSubscriptions = await stripe.subscriptions.list({
|
||||
customer: stripeCustomerId,
|
||||
});
|
||||
|
||||
@ -6,7 +6,7 @@ import type { Subscription } from '@documenso/prisma/client';
|
||||
import { getEnterprisePlanPriceIds } from '../stripe/get-enterprise-plan-prices';
|
||||
|
||||
export type IsUserEnterpriseOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -15,6 +15,6 @@
|
||||
"eslint-plugin-package-json": "^0.10.4",
|
||||
"eslint-plugin-react": "^7.34.0",
|
||||
"eslint-plugin-unused-imports": "^3.1.0",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.7.2"
|
||||
}
|
||||
}
|
||||
@ -126,7 +126,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
id: Number(user.id),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified?.toISOString() ?? null,
|
||||
@ -140,7 +140,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
id: Number(profile.sub),
|
||||
name: profile.name || `${profile.given_name} ${profile.family_name}`.trim(),
|
||||
email: profile.email,
|
||||
emailVerified: profile.email_verified ? new Date().toISOString() : null,
|
||||
@ -274,7 +274,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
});
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
id: Number(user.id),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified?.toISOString() ?? null,
|
||||
@ -308,7 +308,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
|
||||
const { userId, email } = parsedCredential;
|
||||
|
||||
if (typeof userId !== 'string' || typeof email !== 'string') {
|
||||
if (typeof userId !== 'number' || typeof email !== 'string') {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
@ -323,7 +323,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
id: Number(user.id),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified?.toISOString() ?? null,
|
||||
@ -340,7 +340,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
} satisfies JWT;
|
||||
|
||||
if (!merged.email || typeof merged.emailVerified !== 'string') {
|
||||
const userId = merged.id ?? token.sub;
|
||||
const userId = Number(merged.id ?? token.sub);
|
||||
|
||||
const retrieved = await prisma.user.findFirst({
|
||||
where: {
|
||||
@ -367,7 +367,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: {
|
||||
id: merged.id,
|
||||
id: Number(merged.id),
|
||||
},
|
||||
data: {
|
||||
lastSignedIn: merged.lastSignedIn,
|
||||
@ -384,7 +384,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: merged.id,
|
||||
id: Number(merged.id),
|
||||
},
|
||||
data: {
|
||||
emailVerified: merged.emailVerified,
|
||||
@ -407,7 +407,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
return {
|
||||
...session,
|
||||
user: {
|
||||
id: token.id,
|
||||
id: Number(token.id),
|
||||
name: token.name,
|
||||
email: token.email,
|
||||
emailVerified: token.emailVerified ?? null,
|
||||
|
||||
@ -9,7 +9,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
|
||||
type CreatePasskeyAuthenticationOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* The ID of the passkey to request authentication for.
|
||||
|
||||
@ -8,7 +8,7 @@ import { PASSKEY_TIMEOUT } from '../../constants/auth';
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
|
||||
type CreatePasskeyRegistrationOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const createPasskeyRegistrationOptions = async ({
|
||||
|
||||
@ -10,7 +10,7 @@ import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getAuthenticatorOptions } from '../../utils/authenticator';
|
||||
|
||||
type CreatePasskeyOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
passkeyName: string;
|
||||
verificationResponse: RegistrationResponseJSON;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@ -4,7 +4,7 @@ import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
export interface DeletePasskeyOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
passkeyId: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { Prisma } from '@documenso/prisma/client';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
export interface FindPasskeysOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
|
||||
@ -11,7 +11,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendConfirmationEmailProps {
|
||||
userId: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailProps) => {
|
||||
|
||||
@ -11,7 +11,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendForgotPasswordOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {
|
||||
|
||||
@ -8,7 +8,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendResetPasswordOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
|
||||
|
||||
@ -4,7 +4,7 @@ import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
export interface UpdateAuthenticatorsOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
passkeyId: string;
|
||||
name: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@ -25,7 +25,7 @@ export type CreateDocumentMetaOptions = {
|
||||
distributionMethod?: DocumentDistributionMethod;
|
||||
typedSignatureEnabled?: boolean;
|
||||
language?: SupportedLanguageCodes;
|
||||
userId: string;
|
||||
userId: number;
|
||||
requestMetadata: RequestMetadata;
|
||||
};
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
export type CreateDocumentOptions = {
|
||||
title: string;
|
||||
externalId?: string | null;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
documentDataId: string;
|
||||
formValues?: Record<string, string | number | boolean>;
|
||||
|
||||
@ -29,7 +29,7 @@ import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-t
|
||||
|
||||
export type DeleteDocumentOptions = {
|
||||
id: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
@ -7,7 +7,7 @@ import { getDocumentWhereInput } from './get-document-by-id';
|
||||
|
||||
export interface DuplicateDocumentOptions {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import type { FindResultResponse } from '../../types/search-params';
|
||||
import { parseDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
|
||||
export interface FindDocumentAuditLogsOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
documentId: number;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
|
||||
@ -27,7 +27,7 @@ import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-token
|
||||
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
|
||||
|
||||
export type FindDocumentsOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
templateId?: number;
|
||||
source?: DocumentSource;
|
||||
|
||||
@ -10,7 +10,7 @@ import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type GetDocumentByIdOptions = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
@ -58,7 +58,7 @@ export const getDocumentById = async ({ documentId, userId, teamId }: GetDocumen
|
||||
|
||||
export type GetDocumentWhereInputOptions = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
|
||||
/**
|
||||
|
||||
@ -14,7 +14,7 @@ import { getDocumentWhereInput } from './get-document-by-id';
|
||||
|
||||
export type GetDocumentWithDetailsByIdOptions = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -170,7 +170,7 @@ type GetTeamCountsOption = {
|
||||
teamEmail?: string;
|
||||
senderIds?: number[];
|
||||
currentUserEmail: string;
|
||||
userId: string;
|
||||
userId: number;
|
||||
createdAt: Prisma.DocumentWhereInput['createdAt'];
|
||||
currentTeamMemberRole?: TeamMemberRole;
|
||||
search?: string;
|
||||
|
||||
@ -124,7 +124,7 @@ type VerifyPasskeyOptions = {
|
||||
/**
|
||||
* The ID of the user who initiated the request.
|
||||
*/
|
||||
userId: string;
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* The secondary ID of the verification token.
|
||||
|
||||
@ -11,7 +11,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
export type MoveDocumentToTeamOptions = {
|
||||
documentId: number;
|
||||
teamId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ import { getDocumentWhereInput } from './get-document-by-id';
|
||||
|
||||
export type ResendDocumentOptions = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
recipients: number[];
|
||||
teamId?: number;
|
||||
requestMetadata: RequestMetadata;
|
||||
|
||||
@ -8,7 +8,7 @@ import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
export type SearchDocumentsWithKeywordOptions = {
|
||||
query: string;
|
||||
userId: string;
|
||||
userId: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
export type SendDocumentOptions = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
sendEmail?: boolean;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@ -18,7 +18,7 @@ import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../t
|
||||
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
|
||||
export type UpdateDocumentSettingsOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
documentId: number;
|
||||
data: {
|
||||
|
||||
@ -7,7 +7,7 @@ import { prisma } from '@documenso/prisma';
|
||||
export type UpdateDocumentOptions = {
|
||||
documentId: number;
|
||||
data: Prisma.DocumentUpdateInput;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type UpdateTitleOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
documentId: number;
|
||||
title: string;
|
||||
|
||||
@ -16,7 +16,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
|
||||
export type CreateFieldOptions = {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
recipientId: number;
|
||||
type: FieldType;
|
||||
|
||||
@ -7,7 +7,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
export type DeleteFieldOptions = {
|
||||
fieldId: number;
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export type GetFieldByIdOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
fieldId: number;
|
||||
documentId?: number;
|
||||
|
||||
@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface GetFieldsForDocumentOptions {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export type DocumentField = Awaited<ReturnType<typeof getFieldsForDocument>>[number];
|
||||
|
||||
@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface GetFieldsForTemplateOptions {
|
||||
templateId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const getFieldsForTemplate = async ({ templateId, userId }: GetFieldsForTemplateOptions) => {
|
||||
|
||||
@ -30,7 +30,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||
|
||||
export interface SetFieldsForDocumentOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
documentId: number;
|
||||
fields: FieldData[];
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@ -19,7 +19,7 @@ import { FieldType } from '@documenso/prisma/client';
|
||||
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
export type SetFieldsForTemplateOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
templateId: number;
|
||||
fields: {
|
||||
id?: number | null;
|
||||
|
||||
@ -9,7 +9,7 @@ import { createDocumentAuditLogData, diffFieldChanges } from '../../utils/docume
|
||||
export type UpdateFieldOptions = {
|
||||
fieldId: number;
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
recipientId?: number;
|
||||
type?: FieldType;
|
||||
|
||||
@ -6,7 +6,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
export type SetAvatarImageOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number | null;
|
||||
bytes?: string | null;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@ -14,7 +14,7 @@ type TimeConstants = typeof timeConstants & {
|
||||
};
|
||||
|
||||
type CreateApiTokenInput = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
tokenName: string;
|
||||
expiresIn: string | null;
|
||||
|
||||
@ -3,7 +3,7 @@ import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
export type DeleteTokenByIdOptions = {
|
||||
id: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
export type GetUserTokensOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type GetUserTokensOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const getUserTokens = async ({ userId }: GetUserTokensOptions) => {
|
||||
|
||||
@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type GetApiTokenByIdOptions = {
|
||||
id: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const getApiTokenById = async ({ id, userId }: GetApiTokenByIdOptions) => {
|
||||
|
||||
@ -8,7 +8,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
export type DeleteRecipientOptions = {
|
||||
documentId: number;
|
||||
recipientId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
@ -7,7 +7,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export type GetRecipientByIdOptions = {
|
||||
recipientId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface GetRecipientsForDocumentOptions {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface GetRecipientsForTemplateOptions {
|
||||
templateId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export const getRecipientsForTemplate = async ({
|
||||
|
||||
@ -34,7 +34,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||
|
||||
export interface SetRecipientsForDocumentOptions {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
documentId: number;
|
||||
recipients: RecipientData[];
|
||||
|
||||
@ -19,7 +19,7 @@ import { nanoid } from '../../universal/id';
|
||||
import { createRecipientAuthOptions } from '../../utils/document-auth';
|
||||
|
||||
export type SetRecipientsForTemplateOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
templateId: number;
|
||||
recipients: {
|
||||
|
||||
@ -20,7 +20,7 @@ export type UpdateRecipientOptions = {
|
||||
role?: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes | null;
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ export type CreateSharingIdOptions =
|
||||
}
|
||||
| {
|
||||
documentId: number;
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const createOrGetShareLink = async ({ documentId, ...options }: CreateSharingIdOptions) => {
|
||||
|
||||
@ -3,7 +3,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import type { TSiteSettingSchema } from './schema';
|
||||
|
||||
export type UpsertSiteSettingOptions = TSiteSettingSchema & {
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const upsertSiteSetting = async ({
|
||||
|
||||
@ -4,7 +4,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { SubscriptionStatus } from '@documenso/prisma/client';
|
||||
|
||||
export type GetActiveSubscriptionsByUserIdOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const getActiveSubscriptionsByUserId = async ({
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type GetSubscriptionsByUserIdOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const getSubscriptionsByUserId = async ({ userId }: GetSubscriptionsByUserIdOptions) => {
|
||||
|
||||
@ -6,7 +6,7 @@ import { TeamMemberInviteStatus } from '@documenso/prisma/client';
|
||||
import { jobs } from '../../jobs/client';
|
||||
|
||||
export type AcceptTeamInvitationOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type CreateTeamBillingPortalOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type CreateTeamPendingCheckoutSession = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
pendingTeamId: number;
|
||||
interval: 'monthly' | 'yearly';
|
||||
};
|
||||
|
||||
@ -19,7 +19,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||
|
||||
export type CreateTeamEmailVerificationOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
data: {
|
||||
email: string;
|
||||
|
||||
@ -20,7 +20,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||
|
||||
export type CreateTeamMemberInvitesOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
userName: string;
|
||||
teamId: number;
|
||||
invitations: TCreateTeamMemberInvitesMutationSchema['invitations'];
|
||||
|
||||
@ -16,7 +16,7 @@ export type CreateTeamOptions = {
|
||||
/**
|
||||
* ID of the user creating the Team.
|
||||
*/
|
||||
userId: string;
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* Name of the team to display.
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type DeclineTeamInvitationOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type DeleteTeamEmailVerificationOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||
|
||||
export type DeleteTeamEmailOptions = {
|
||||
userId: string;
|
||||
userId: number;
|
||||
userEmail: string;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ export type DeleteTeamMemberInvitationsOptions = {
|
||||
/**
|
||||
* The ID of the user who is initiating this action.
|
||||
*/
|
||||
userId: string;
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* The ID of the team to remove members from.
|
||||
|
||||
@ -9,7 +9,7 @@ export type DeleteTeamMembersOptions = {
|
||||
/**
|
||||
* The ID of the user who is initiating this action.
|
||||
*/
|
||||
userId: string;
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* The ID of the team to remove members from.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user