mirror of
https://github.com/documenso/documenso.git
synced 2026-08-26 00:02:43 +10:00
feat: last used column for api tokens (#3230)
This commit is contained in:
@@ -68,6 +68,20 @@ export default function ApiTokensPage() {
|
|||||||
return i18n.date(row.original.expires);
|
return i18n.date(row.original.expires);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: t`Last Used`,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
if (!row.original.lastUsedAt) {
|
||||||
|
return (
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
<Trans>Never</Trans>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className="text-foreground">{i18n.date(row.original.lastUsedAt)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: t`Actions`,
|
header: t`Actions`,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
@@ -146,6 +160,9 @@ export default function ApiTokensPage() {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<Skeleton className="h-4 w-16 rounded-full" />
|
<Skeleton className="h-4 w-16 rounded-full" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-4 w-16 rounded-full" />
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Skeleton className="h-4 w-12 rounded-full" />
|
<Skeleton className="h-4 w-12 rounded-full" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { logger } from '../../utils/logger';
|
||||||
import { hashString } from '../auth/hash';
|
import { hashString } from '../auth/hash';
|
||||||
import { assertOrganisationRatesAndLimits } from '../rate-limit/assert-organisation-rates-and-limits';
|
import { assertOrganisationRatesAndLimits } from '../rate-limit/assert-organisation-rates-and-limits';
|
||||||
|
|
||||||
|
const LAST_USED_AT_UPDATE_INTERVAL = 60_000; // 1 minute
|
||||||
|
|
||||||
type GetApiTokenByTokenOptions = {
|
type GetApiTokenByTokenOptions = {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
@@ -96,6 +99,29 @@ export const getApiTokenByToken = async ({ token, bypassRateLimit = false }: Get
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only update the lastUsedAt after X amount of time has passed to reduce
|
||||||
|
// the number of writes to the database
|
||||||
|
if (!apiToken.lastUsedAt || apiToken.lastUsedAt.getTime() + LAST_USED_AT_UPDATE_INTERVAL < Date.now()) {
|
||||||
|
void prisma.apiToken
|
||||||
|
.updateMany({
|
||||||
|
where: {
|
||||||
|
id: apiToken.id,
|
||||||
|
// Optimistic guard: skip if another request beat us
|
||||||
|
lastUsedAt: apiToken.lastUsedAt,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
lastUsedAt: new Date(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.warn({
|
||||||
|
msg: 'Failed to update API token lastUsedAt',
|
||||||
|
apiTokenId: apiToken.id,
|
||||||
|
err,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...apiToken,
|
...apiToken,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const getApiTokens = async ({ userId, teamId }: GetApiTokensOptions) => {
|
|||||||
name: true,
|
name: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
expires: true,
|
expires: true,
|
||||||
|
lastUsedAt: true,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
|
|||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ApiToken" ADD COLUMN "lastUsedAt" TIMESTAMP(3);
|
||||||
@@ -226,6 +226,7 @@ model ApiToken {
|
|||||||
algorithm ApiTokenAlgorithm @default(SHA512)
|
algorithm ApiTokenAlgorithm @default(SHA512)
|
||||||
expires DateTime?
|
expires DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
lastUsedAt DateTime?
|
||||||
userId Int?
|
userId Int?
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
teamId Int
|
teamId Int
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const ZGetApiTokensResponseSchema = z.array(
|
|||||||
name: true,
|
name: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
expires: true,
|
expires: true,
|
||||||
|
lastUsedAt: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user