add crud for adding providers and models to resources

This commit is contained in:
miloschwartz
2026-08-04 15:54:24 -04:00
parent ed8545f8a2
commit e38359c74f
39 changed files with 2343 additions and 438 deletions
+6 -19
View File
@@ -9,26 +9,16 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { and, eq } from "drizzle-orm";
import type { CreateOrEditAiModelResponse } from "@server/routers/aiProvider/types";
import {
aiBudgetUnitSchema,
refineBudgetFields
} from "@server/routers/aiProvider/validation";
const paramsSchema = z.strictObject({
providerId: z.coerce.number().int().positive()
});
const bodySchema = z
.strictObject({
modelKey: z.string().nonempty(),
name: z.string().nonempty(),
budgetAmount: z.number().positive().optional().nullable(),
budgetUnit: aiBudgetUnitSchema.optional().nullable(),
enabled: z.boolean().optional()
})
.superRefine((data, ctx) => {
refineBudgetFields(data, ctx);
});
const bodySchema = z.strictObject({
modelKey: z.string().nonempty(),
name: z.string().nonempty(),
enabled: z.boolean().optional()
});
registry.registerPath({
method: "put",
@@ -79,8 +69,7 @@ export async function createAiModel(
}
const { providerId } = parsedParams.data;
const { modelKey, name, budgetAmount, budgetUnit, enabled } =
parsedBody.data;
const { modelKey, name, enabled } = parsedBody.data;
const [provider] =
req.aiProvider && req.aiProvider.providerId === providerId
@@ -127,8 +116,6 @@ export async function createAiModel(
providerId,
modelKey,
name,
budgetAmount: budgetAmount ?? null,
budgetUnit: budgetUnit ?? null,
enabled: enabled ?? true,
createdAt: now,
updatedAt: now
@@ -13,10 +13,8 @@ import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/
import { toPublicAiProvider } from "@server/routers/aiProvider/types";
import {
aiAuthTypeSchema,
aiBudgetUnitSchema,
aiProviderTypeSchema,
aiRoutingModeSchema,
refineBudgetFields,
refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation";
@@ -33,13 +31,10 @@ const bodySchema = z
authType: aiAuthTypeSchema.optional().nullable(),
routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(),
budgetAmount: z.number().positive().optional().nullable(),
budgetUnit: aiBudgetUnitSchema.optional().nullable(),
enabled: z.boolean().optional()
})
.superRefine((data, ctx) => {
refineProviderUpstreamFields(data, ctx);
refineBudgetFields(data, ctx);
});
registry.registerPath({
@@ -99,8 +94,6 @@ export async function createAiProvider(
authType,
routingMode,
skipTlsVerification,
budgetAmount,
budgetUnit,
enabled
} = parsedBody.data;
@@ -126,8 +119,6 @@ export async function createAiProvider(
authType: authType ?? null,
routingMode: resolvedRoutingMode,
skipTlsVerification: skipTlsVerification ?? false,
budgetAmount: budgetAmount ?? null,
budgetUnit: budgetUnit ?? null,
enabled: enabled ?? true,
createdAt: now,
updatedAt: now
+5 -21
View File
@@ -9,26 +9,16 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { and, eq, ne } from "drizzle-orm";
import type { CreateOrEditAiModelResponse } from "@server/routers/aiProvider/types";
import {
aiBudgetUnitSchema,
refineBudgetFields
} from "@server/routers/aiProvider/validation";
const paramsSchema = z.strictObject({
modelId: z.coerce.number().int().positive()
});
const bodySchema = z
.strictObject({
modelKey: z.string().nonempty().optional(),
name: z.string().nonempty().optional(),
budgetAmount: z.number().positive().optional().nullable(),
budgetUnit: aiBudgetUnitSchema.optional().nullable(),
enabled: z.boolean().optional()
})
.superRefine((data, ctx) => {
refineBudgetFields(data, ctx);
});
const bodySchema = z.strictObject({
modelKey: z.string().nonempty().optional(),
name: z.string().nonempty().optional(),
enabled: z.boolean().optional()
});
registry.registerPath({
method: "post",
@@ -135,12 +125,6 @@ export async function updateAiModel(
if (body.name !== undefined) {
updateData.name = body.name;
}
if (body.budgetAmount !== undefined) {
updateData.budgetAmount = body.budgetAmount;
}
if (body.budgetUnit !== undefined) {
updateData.budgetUnit = body.budgetUnit;
}
if (body.enabled !== undefined) {
updateData.enabled = body.enabled;
}
+9 -23
View File
@@ -14,10 +14,8 @@ import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/
import { toPublicAiProvider } from "@server/routers/aiProvider/types";
import {
aiAuthTypeSchema,
aiBudgetUnitSchema,
aiProviderTypeSchema,
aiRoutingModeSchema,
refineBudgetFields,
refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation";
import type {
@@ -29,21 +27,15 @@ const paramsSchema = z.strictObject({
providerId: z.coerce.number().int().positive()
});
const bodySchema = z
.strictObject({
name: z.string().nonempty().optional(),
upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional().nullable(),
routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(),
budgetAmount: z.number().positive().optional().nullable(),
budgetUnit: aiBudgetUnitSchema.optional().nullable(),
enabled: z.boolean().optional()
})
.superRefine((data, ctx) => {
refineBudgetFields(data, ctx);
});
const bodySchema = z.strictObject({
name: z.string().nonempty().optional(),
upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional().nullable(),
routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(),
enabled: z.boolean().optional()
});
registry.registerPath({
method: "post",
@@ -168,12 +160,6 @@ export async function updateAiProvider(
if (body.enabled !== undefined) {
updateData.enabled = body.enabled;
}
if (body.budgetAmount !== undefined) {
updateData.budgetAmount = body.budgetAmount;
}
if (body.budgetUnit !== undefined) {
updateData.budgetUnit = body.budgetUnit;
}
if (nextRoutingMode === "target") {
updateData.upstreamUrl = null;
} else if (body.upstreamUrl !== undefined) {
-24
View File
@@ -1,7 +1,6 @@
import { z } from "zod";
import {
providerRequiresUpstreamUrl,
type AiBudgetUnit,
type AiProviderRoutingMode,
type AiProviderType
} from "@server/lib/aiProviderDefaults";
@@ -18,33 +17,10 @@ export const aiProviderTypeSchema = z.enum([
"custom"
]);
export const aiBudgetUnitSchema = z.enum(["usd", "tokens"]);
export const aiAuthTypeSchema = z.enum(["bearer"]);
export const aiRoutingModeSchema = z.enum(["url", "target"]);
export function refineBudgetFields(
data: {
budgetAmount?: number | null;
budgetUnit?: AiBudgetUnit | null;
},
ctx: z.RefinementCtx
) {
const hasAmount =
data.budgetAmount !== undefined && data.budgetAmount !== null;
const hasUnit = data.budgetUnit !== undefined && data.budgetUnit !== null;
if (hasAmount !== hasUnit) {
ctx.addIssue({
code: "custom",
message:
"budgetAmount and budgetUnit must both be set or both omitted",
path: hasAmount ? ["budgetUnit"] : ["budgetAmount"]
});
}
}
export function refineProviderUpstreamFields(
data: {
type: AiProviderType;