Files
veridian/server/api/provider/[providerId]/index.patch.ts
T
2026-10-15 10:22:06 -05:00

64 lines
2.6 KiB
TypeScript

import { and, eq, gt } from "drizzle-orm";
import * as z from "zod";
import { providers, sessions } from "~~/drizzle/schema";
import { lockAccount } from "~~/server/lib/account-security";
import { db } from "~~/server/lib/db";
export default defineEventHandler(async (event) => {
const { user, session } = await protectRoute(event);
const result = await readValidatedBody(event, (body) => z.object({
config: z.object({
apiKey: z.string().optional(),
apiProxyUrl: z.string().optional(),
}).optional(),
expectedUpdatedAt: z.iso.datetime().optional(),
enabled: z.boolean().optional(),
}).refine(body => body.config === undefined || body.expectedUpdatedAt !== undefined, {
message: "Provider config updates require a revision",
}).safeParse(body));
if (!result.success) {
throw createError({ statusCode: 400, message: result.error.issues[0]?.message });
}
const providerId = getRouterParam(event, "providerId");
if (!providerId) {
throw createError({ statusCode: 400, statusMessage: "Invalid provider" });
}
const { config, enabled, expectedUpdatedAt } = result.data;
return await db.transaction(async (tx) => {
await lockAccount(tx, user.id);
// A request may have waited behind a password rotation or recovery.
const [activeSession] = await tx.select({ id: sessions.id }).from(sessions).where(and(
eq(sessions.id, session.id), eq(sessions.userId, user.id), gt(sessions.expiresAt, new Date()),
));
if (!activeSession) {
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
}
const [existing] = await tx.select().from(providers).where(and(
eq(providers.id, providerId), eq(providers.userId, user.id),
));
if (!existing) {
throw createError({ statusCode: 404, statusMessage: "Invalid provider" });
}
if (config !== undefined && existing.updatedAt.getTime() !== new Date(expectedUpdatedAt ?? "").getTime()) {
throw createError({
statusCode: 409,
statusMessage: "Provider config out of date",
data: { code: "PROVIDER_KEYS_OUT_OF_DATE" },
});
}
const updatedAt = new Date(Math.max(Date.now(), existing.updatedAt.getTime() + 1));
await tx.update(providers).set({
config: config === undefined ? undefined : { ...existing.config, ...config },
enabled,
updatedAt,
}).where(and(eq(providers.id, providerId), eq(providers.userId, user.id)));
return { ok: true, updatedAt };
});
});