fix: update teams API tokens logic
This commit is contained in:
@@ -30,22 +30,20 @@ import {
|
|||||||
import { Input } from '@documenso/ui/primitives/input';
|
import { Input } from '@documenso/ui/primitives/input';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
export type TokenDeleteDialogProps = {
|
export type TokenDeleteDialogProps = {
|
||||||
teamId?: number;
|
|
||||||
token: Pick<ApiToken, 'id' | 'name'>;
|
token: Pick<ApiToken, 'id' | 'name'>;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TokenDeleteDialog({
|
export default function TokenDeleteDialog({ token, onDelete, children }: TokenDeleteDialogProps) {
|
||||||
teamId,
|
|
||||||
token,
|
|
||||||
onDelete,
|
|
||||||
children,
|
|
||||||
}: TokenDeleteDialogProps) {
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const team = useOptionalCurrentTeam();
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const deleteMessage = _(msg`delete ${token.name}`);
|
const deleteMessage = _(msg`delete ${token.name}`);
|
||||||
@@ -75,7 +73,7 @@ export default function TokenDeleteDialog({
|
|||||||
try {
|
try {
|
||||||
await deleteTokenMutation({
|
await deleteTokenMutation({
|
||||||
id: token.id,
|
id: token.id,
|
||||||
teamId,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useTransition } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@@ -38,6 +38,8 @@ import {
|
|||||||
import { Switch } from '@documenso/ui/primitives/switch';
|
import { Switch } from '@documenso/ui/primitives/switch';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
export const EXPIRATION_DATES = {
|
export const EXPIRATION_DATES = {
|
||||||
ONE_WEEK: msg`7 days`,
|
ONE_WEEK: msg`7 days`,
|
||||||
ONE_MONTH: msg`1 month`,
|
ONE_MONTH: msg`1 month`,
|
||||||
@@ -59,15 +61,14 @@ type NewlyCreatedToken = {
|
|||||||
|
|
||||||
export type ApiTokenFormProps = {
|
export type ApiTokenFormProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
teamId?: number;
|
|
||||||
tokens?: Pick<ApiToken, 'id'>[];
|
tokens?: Pick<ApiToken, 'id'>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) => {
|
export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
|
||||||
const [isTransitionPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const [, copy] = useCopyToClipboard();
|
const [, copy] = useCopyToClipboard();
|
||||||
|
|
||||||
|
const team = useOptionalCurrentTeam();
|
||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
|
|||||||
const onSubmit = async ({ tokenName, expirationDate }: TCreateTokenMutationSchema) => {
|
const onSubmit = async ({ tokenName, expirationDate }: TCreateTokenMutationSchema) => {
|
||||||
try {
|
try {
|
||||||
await createTokenMutation({
|
await createTokenMutation({
|
||||||
teamId,
|
teamId: team?.id,
|
||||||
tokenName,
|
tokenName,
|
||||||
expirationDate: noExpirationDate ? null : expirationDate,
|
expirationDate: noExpirationDate ? null : expirationDate,
|
||||||
});
|
});
|
||||||
@@ -238,7 +239,7 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="hidden md:inline-flex"
|
className="hidden md:inline-flex"
|
||||||
disabled={!form.formState.isDirty}
|
disabled={!form.formState.isDirty}
|
||||||
loading={form.formState.isSubmitting || isTransitionPending}
|
loading={form.formState.isSubmitting}
|
||||||
>
|
>
|
||||||
<Trans>Create token</Trans>
|
<Trans>Create token</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -247,7 +248,7 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!form.formState.isDirty}
|
disabled={!form.formState.isDirty}
|
||||||
loading={form.formState.isSubmitting || isTransitionPending}
|
loading={form.formState.isSubmitting}
|
||||||
>
|
>
|
||||||
<Trans>Create token</Trans>
|
<Trans>Create token</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import React from 'react';
|
|||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
|
||||||
export type SettingsHeaderProps = {
|
export type SettingsHeaderProps = {
|
||||||
title: string;
|
title: string | React.ReactNode;
|
||||||
subtitle: string;
|
subtitle: string | React.ReactNode;
|
||||||
hideDivider?: boolean;
|
hideDivider?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -1,90 +1,115 @@
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
import { TeamMemberRole } from '@prisma/client';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
|
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||||
|
import { AlertTitle } from '@documenso/ui/primitives/alert';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
|
||||||
import TokenDeleteDialog from '~/components/dialogs/token-delete-dialog';
|
import TokenDeleteDialog from '~/components/dialogs/token-delete-dialog';
|
||||||
import { ApiTokenForm } from '~/components/forms/token';
|
import { ApiTokenForm } from '~/components/forms/token';
|
||||||
|
import { SettingsHeader } from '~/components/general/settings-header';
|
||||||
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
export default function ApiTokensPage() {
|
export default function ApiTokensPage() {
|
||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
|
|
||||||
const { data: tokens } = trpc.apiToken.getTokens.useQuery();
|
const { data: tokens } = trpc.apiToken.getTokens.useQuery();
|
||||||
|
|
||||||
|
const team = useOptionalCurrentTeam();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl font-semibold">
|
<SettingsHeader
|
||||||
<Trans>API Tokens</Trans>
|
title={<Trans>API Tokens</Trans>}
|
||||||
</h3>
|
subtitle={
|
||||||
|
<Trans>
|
||||||
|
On this page, you can create and manage API tokens. See our{' '}
|
||||||
|
<a
|
||||||
|
className="text-primary underline"
|
||||||
|
href={'https://docs.documenso.com/developers/public-api'}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Documentation
|
||||||
|
</a>{' '}
|
||||||
|
for more information.
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
{team && team?.currentTeamMember.role !== TeamMemberRole.ADMIN ? (
|
||||||
<Trans>
|
<Alert
|
||||||
On this page, you can create new API tokens and manage the existing ones. <br />
|
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||||
Also see our{' '}
|
variant="warning"
|
||||||
<a
|
>
|
||||||
className="text-primary underline"
|
<div>
|
||||||
href={'https://docs.documenso.com/developers/public-api'}
|
<AlertTitle>
|
||||||
target="_blank"
|
<Trans>Unauthorized</Trans>
|
||||||
>
|
</AlertTitle>
|
||||||
Documentation
|
<AlertDescription className="mr-2">
|
||||||
</a>
|
<Trans>You need to be an admin to manage API tokens.</Trans>
|
||||||
.
|
</AlertDescription>
|
||||||
</Trans>
|
</div>
|
||||||
</p>
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ApiTokenForm className="max-w-xl" tokens={tokens} />
|
||||||
|
|
||||||
<hr className="my-4" />
|
<hr className="mb-4 mt-8" />
|
||||||
|
|
||||||
<ApiTokenForm className="max-w-xl" tokens={tokens} />
|
<h4 className="text-xl font-medium">
|
||||||
|
<Trans>Your existing tokens</Trans>
|
||||||
|
</h4>
|
||||||
|
|
||||||
<hr className="mb-4 mt-8" />
|
{tokens && tokens.length === 0 && (
|
||||||
|
<div className="mb-4">
|
||||||
<h4 className="text-xl font-medium">
|
<p className="text-muted-foreground mt-2 text-sm italic">
|
||||||
<Trans>Your existing tokens</Trans>
|
<Trans>Your tokens will be shown here once you create them.</Trans>
|
||||||
</h4>
|
</p>
|
||||||
|
|
||||||
{tokens && tokens.length === 0 && (
|
|
||||||
<div className="mb-4">
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm italic">
|
|
||||||
<Trans>Your tokens will be shown here once you create them.</Trans>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{tokens && tokens.length > 0 && (
|
|
||||||
<div className="mt-4 flex max-w-xl flex-col gap-y-4">
|
|
||||||
{tokens.map((token) => (
|
|
||||||
<div key={token.id} className="border-border rounded-lg border p-4">
|
|
||||||
<div className="flex items-center justify-between gap-x-4">
|
|
||||||
<div>
|
|
||||||
<h5 className="text-base">{token.name}</h5>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-xs">
|
|
||||||
<Trans>Created on {i18n.date(token.createdAt, DateTime.DATETIME_FULL)}</Trans>
|
|
||||||
</p>
|
|
||||||
{token.expires ? (
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
|
||||||
<Trans>Expires on {i18n.date(token.expires, DateTime.DATETIME_FULL)}</Trans>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
|
||||||
<Trans>Token doesn't have an expiration date</Trans>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TokenDeleteDialog token={token}>
|
|
||||||
<Button variant="destructive">
|
|
||||||
<Trans>Delete</Trans>
|
|
||||||
</Button>
|
|
||||||
</TokenDeleteDialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>
|
|
||||||
|
{tokens && tokens.length > 0 && (
|
||||||
|
<div className="mt-4 flex max-w-xl flex-col gap-y-4">
|
||||||
|
{tokens.map((token) => (
|
||||||
|
<div key={token.id} className="border-border rounded-lg border p-4">
|
||||||
|
<div className="flex items-center justify-between gap-x-4">
|
||||||
|
<div>
|
||||||
|
<h5 className="text-base">{token.name}</h5>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground mt-2 text-xs">
|
||||||
|
<Trans>
|
||||||
|
Created on {i18n.date(token.createdAt, DateTime.DATETIME_FULL)}
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
{token.expires ? (
|
||||||
|
<p className="text-muted-foreground mt-1 text-xs">
|
||||||
|
<Trans>
|
||||||
|
Expires on {i18n.date(token.expires, DateTime.DATETIME_FULL)}
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-muted-foreground mt-1 text-xs">
|
||||||
|
<Trans>Token doesn't have an expiration date</Trans>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TokenDeleteDialog token={token}>
|
||||||
|
<Button variant="destructive">
|
||||||
|
<Trans>Delete</Trans>
|
||||||
|
</Button>
|
||||||
|
</TokenDeleteDialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,126 +1,3 @@
|
|||||||
import { useLingui } from '@lingui/react';
|
import ApiTokensPage from '~/routes/_authenticated+/settings+/tokens+/index';
|
||||||
import { Trans } from '@lingui/react/macro';
|
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
|
|
||||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
export default ApiTokensPage;
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
|
||||||
import { getTeamTokens } from '@documenso/lib/server-only/public-api/get-all-team-tokens';
|
|
||||||
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
|
||||||
|
|
||||||
import TokenDeleteDialog from '~/components/dialogs/token-delete-dialog';
|
|
||||||
import { ApiTokenForm } from '~/components/forms/token';
|
|
||||||
|
|
||||||
import type { Route } from './+types/tokens';
|
|
||||||
|
|
||||||
// Todo: This can be optimized.
|
|
||||||
export async function loader({ request, params }: Route.LoaderArgs) {
|
|
||||||
const { user } = await getSession(request);
|
|
||||||
|
|
||||||
const team = await getTeamByUrl({
|
|
||||||
userId: user.id,
|
|
||||||
teamUrl: params.teamUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tokens = await getTeamTokens({ userId: user.id, teamId: team.id }).catch(() => null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
team,
|
|
||||||
tokens,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ApiTokensPage({ loaderData }: Route.ComponentProps) {
|
|
||||||
const { i18n } = useLingui();
|
|
||||||
|
|
||||||
const { team, tokens } = loaderData;
|
|
||||||
|
|
||||||
if (!tokens) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl font-semibold">
|
|
||||||
<Trans>API Tokens</Trans>
|
|
||||||
</h3>
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
|
||||||
<Trans>Something went wrong.</Trans>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl font-semibold">
|
|
||||||
<Trans>API Tokens</Trans>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
|
||||||
<Trans>
|
|
||||||
On this page, you can create new API tokens and manage the existing ones. <br />
|
|
||||||
You can view our swagger docs{' '}
|
|
||||||
<a
|
|
||||||
className="text-primary underline"
|
|
||||||
href={`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/openapi`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
here
|
|
||||||
</a>
|
|
||||||
</Trans>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<hr className="my-4" />
|
|
||||||
|
|
||||||
<ApiTokenForm className="max-w-xl" teamId={team.id} tokens={tokens} />
|
|
||||||
|
|
||||||
<hr className="mb-4 mt-8" />
|
|
||||||
|
|
||||||
<h4 className="text-xl font-medium">
|
|
||||||
<Trans>Your existing tokens</Trans>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
{tokens.length === 0 && (
|
|
||||||
<div className="mb-4">
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm italic">
|
|
||||||
<Trans>Your tokens will be shown here once you create them.</Trans>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{tokens.length > 0 && (
|
|
||||||
<div className="mt-4 flex max-w-xl flex-col gap-y-4">
|
|
||||||
{tokens.map((token) => (
|
|
||||||
<div key={token.id} className="border-border rounded-lg border p-4">
|
|
||||||
<div className="flex items-center justify-between gap-x-4">
|
|
||||||
<div>
|
|
||||||
<h5 className="text-base">{token.name}</h5>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-xs">
|
|
||||||
<Trans>Created on {i18n.date(token.createdAt, DateTime.DATETIME_FULL)}</Trans>
|
|
||||||
</p>
|
|
||||||
{token.expires ? (
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
|
||||||
<Trans>Expires on {i18n.date(token.expires, DateTime.DATETIME_FULL)}</Trans>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
|
||||||
<Trans>Token doesn't have an expiration date</Trans>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TokenDeleteDialog token={token} teamId={team.id}>
|
|
||||||
<Button variant="destructive">
|
|
||||||
<Trans>Delete</Trans>
|
|
||||||
</Button>
|
|
||||||
</TokenDeleteDialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { generateOpenApi } from '@ts-rest/open-api';
|
import { generateOpenApi } from '@ts-rest/open-api';
|
||||||
|
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
|
|
||||||
import { ApiContractV1 } from './contract';
|
import { ApiContractV1 } from './contract';
|
||||||
|
|
||||||
export const OpenAPIV1 = Object.assign(
|
export const OpenAPIV1 = Object.assign(
|
||||||
@@ -11,6 +13,11 @@ export const OpenAPIV1 = Object.assign(
|
|||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'The Documenso API for retrieving, creating, updating and deleting documents.',
|
description: 'The Documenso API for retrieving, creating, updating and deleting documents.',
|
||||||
},
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: NEXT_PUBLIC_WEBAPP_URL(),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
setOperationId: true,
|
setOperationId: true,
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { TeamMemberRole } from '@prisma/client';
|
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
|
|
||||||
export type GetUserTokensOptions = {
|
|
||||||
userId: number;
|
|
||||||
teamId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GetTeamTokensResponse = Awaited<ReturnType<typeof getTeamTokens>>;
|
|
||||||
|
|
||||||
export const getTeamTokens = async ({ userId, teamId }: GetUserTokensOptions) => {
|
|
||||||
const teamMember = await prisma.teamMember.findFirst({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (teamMember?.role !== TeamMemberRole.ADMIN) {
|
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
||||||
message: 'You do not have the required permissions to view this page.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await prisma.apiToken.findMany({
|
|
||||||
where: {
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
algorithm: true,
|
|
||||||
createdAt: true,
|
|
||||||
expires: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
|
|
||||||
export type GetUserTokensOptions = {
|
|
||||||
userId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getUserTokens = async ({ userId }: GetUserTokensOptions) => {
|
|
||||||
return await prisma.apiToken.findMany({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
teamId: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
algorithm: true,
|
|
||||||
createdAt: true,
|
|
||||||
expires: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
39
packages/lib/server-only/public-api/get-api-tokens.ts
Normal file
39
packages/lib/server-only/public-api/get-api-tokens.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
export type GetApiTokensOptions = {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getApiTokens = async ({ userId, teamId }: GetApiTokensOptions) => {
|
||||||
|
return await prisma.apiToken.findMany({
|
||||||
|
where: {
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
role: TeamMemberRole.ADMIN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
createdAt: true,
|
||||||
|
expires: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
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 { 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 { getApiTokenById } from '@documenso/lib/server-only/public-api/get-api-token-by-id';
|
||||||
|
import { getApiTokens } from '@documenso/lib/server-only/public-api/get-api-tokens';
|
||||||
|
|
||||||
import { authenticatedProcedure, router } from '../trpc';
|
import { authenticatedProcedure, router } from '../trpc';
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
|
|
||||||
export const apiTokenRouter = router({
|
export const apiTokenRouter = router({
|
||||||
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
|
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
|
||||||
return await getUserTokens({ userId: ctx.user.id });
|
return await getApiTokens({ userId: ctx.user.id, teamId: ctx.teamId });
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getTokenById: authenticatedProcedure
|
getTokenById: authenticatedProcedure
|
||||||
|
|||||||
Reference in New Issue
Block a user