149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import { App } from "@/pkg/hono/app";
|
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
|
|
import { rootKeyAuth } from "@/pkg/auth/root_key";
|
|
import { UnkeyApiError, openApiErrorResponses } from "@/pkg/errors";
|
|
import { schema } from "@aigxion/db";
|
|
import { keySchema } from "./schema";
|
|
|
|
const route = createRoute({
|
|
method: "get",
|
|
path: "/v1/apis/{apiId}/keys",
|
|
request: {
|
|
header: z.object({
|
|
authorization: z
|
|
.string()
|
|
.regex(/^Bearer [a-zA-Z0-9_]+/)
|
|
.openapi({
|
|
description: "A root key to authorize the request formatted as bearer token",
|
|
example: "Bearer unkey_1234",
|
|
}),
|
|
}),
|
|
params: z.object({
|
|
apiId: z.string().min(1).openapi({
|
|
description: "The id of the api to fetch",
|
|
example: "api_1234",
|
|
}),
|
|
}),
|
|
query: z.object({
|
|
limit: z.coerce.number().int().min(1).max(100).optional().default(100).openapi({
|
|
description: "The maximum number of keys to return",
|
|
example: 100,
|
|
}),
|
|
offset: z.coerce.number().optional().openapi({
|
|
description:
|
|
"Use this to fetch the next page of results. A new cursor will be returned in the response if there are more results.",
|
|
}),
|
|
ownerId: z.string().min(1).optional().openapi({
|
|
description: "If provided, this will only return keys where the `ownerId` matches.",
|
|
}),
|
|
}),
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Keys belonging to the api",
|
|
content: {
|
|
"application/json": {
|
|
schema: z.object({
|
|
keys: z.array(keySchema),
|
|
total: z.number().int().openapi({
|
|
description: "The total number of keys for this api",
|
|
}),
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
...openApiErrorResponses,
|
|
},
|
|
});
|
|
|
|
export type Route = typeof route;
|
|
export type LegacyApisListKeysResponse = z.infer<
|
|
(typeof route.responses)[200]["content"]["application/json"]["schema"]
|
|
>;
|
|
|
|
export const registerLegacyApisListKeys = (app: App) =>
|
|
app.openapi(route, async (c) => {
|
|
const { db, cache } = c.get("services");
|
|
const auth = await rootKeyAuth(c);
|
|
|
|
const apiId = c.req.param("apiId");
|
|
const { limit, offset, ownerId } = c.req.query();
|
|
|
|
const { val: api, err } = await cache.withCache(c, "apiById", apiId, async () => {
|
|
return (
|
|
(await db.query.apis.findFirst({
|
|
where: (table, { eq, and, isNull }) => and(eq(table.id, apiId), isNull(table.deletedAt)),
|
|
})) ?? null
|
|
);
|
|
});
|
|
|
|
if (err) {
|
|
throw new UnkeyApiError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `unable to load api: ${err.message}`,
|
|
});
|
|
}
|
|
if (!api || api.workspaceId !== auth.authorizedWorkspaceId) {
|
|
throw new UnkeyApiError({
|
|
code: "NOT_FOUND",
|
|
message: `api ${apiId} not found`,
|
|
});
|
|
}
|
|
|
|
if (!api.keyAuthId) {
|
|
throw new UnkeyApiError({
|
|
code: "PRECONDITION_FAILED",
|
|
message: `api ${apiId} is not setup to handle keys`,
|
|
});
|
|
}
|
|
const keysWhere: Parameters<typeof and> = [
|
|
isNull(schema.keys.deletedAt),
|
|
eq(schema.keys.keyAuthId, api.keyAuthId),
|
|
];
|
|
if (ownerId) {
|
|
keysWhere.push(eq(schema.keys.ownerId, ownerId));
|
|
}
|
|
|
|
const keys = await db.query.keys.findMany({
|
|
where: and(...keysWhere),
|
|
limit: parseInt(limit),
|
|
orderBy: schema.keys.id,
|
|
offset: offset ? parseInt(offset) : undefined,
|
|
});
|
|
|
|
const total = await db
|
|
// @ts-ignore, mysql sucks
|
|
.select({ count: sql<string>`count(*)` })
|
|
.from(schema.keys)
|
|
.where(and(eq(schema.keys.keyAuthId, api.keyAuthId), isNull(schema.keys.deletedAt)));
|
|
|
|
return c.json({
|
|
keys: keys.map((k) => ({
|
|
id: k.id,
|
|
start: k.start,
|
|
apiId: api.id,
|
|
workspaceId: k.workspaceId,
|
|
name: k.name ?? undefined,
|
|
ownerId: k.ownerId ?? undefined,
|
|
meta: k.meta ? JSON.parse(k.meta) : undefined,
|
|
createdAt: k.createdAt.getTime() ?? undefined,
|
|
expires: k.expires?.getTime() ?? undefined,
|
|
ratelimit:
|
|
k.ratelimitType && k.ratelimitLimit && k.ratelimitRefillRate && k.ratelimitRefillInterval
|
|
? {
|
|
type: k.ratelimitType,
|
|
limit: k.ratelimitLimit,
|
|
refillRate: k.ratelimitRefillRate,
|
|
refillInterval: k.ratelimitRefillInterval,
|
|
}
|
|
: undefined,
|
|
remaining: k.remaining ?? undefined,
|
|
})),
|
|
// @ts-ignore, mysql sucks
|
|
total: parseInt(total.at(0)?.count ?? "0"),
|
|
cursor: keys.at(-1)?.id ?? undefined,
|
|
});
|
|
});
|