add niceId to provider

This commit is contained in:
Owen
2026-08-14 09:30:44 -04:00
parent ee75a09f8c
commit f0f5e9219b
5 changed files with 171 additions and 93 deletions
+27
View File
@@ -1,6 +1,7 @@
import { join } from "path"; import { join } from "path";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { import {
aiProviders,
clients, clients,
db, db,
resourcePolicies, resourcePolicies,
@@ -113,6 +114,32 @@ export async function getUniqueResourceName(orgId: string): Promise<string> {
} }
} }
export async function getUniqueProviderName(orgId: string): Promise<string> {
let loops = 0;
while (true) {
if (loops > 100) {
throw new Error("Could not generate a unique name");
}
const name = generateName();
const aiProviderCount = await db
.select({
niceId: aiProviders.niceId,
orgId: aiProviders.orgId
})
.from(aiProviders)
.where(
and(eq(aiProviders.niceId, name), eq(aiProviders.orgId, orgId))
);
if (aiProviderCount.length === 0) {
return name;
}
loops++;
}
}
export async function getUniqueResourcePolicyName( export async function getUniqueResourcePolicyName(
orgId: string orgId: string
): Promise<string> { ): Promise<string> {
+7 -2
View File
@@ -1684,12 +1684,15 @@ export const statusHistory = pgTable(
] ]
); );
export const aiProviders = pgTable("aiProviders", { export const aiProviders = pgTable(
"aiProviders",
{
providerId: serial("providerId").primaryKey(), providerId: serial("providerId").primaryKey(),
orgId: varchar("orgId") orgId: varchar("orgId")
.notNull() .notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }), .references(() => orgs.orgId, { onDelete: "cascade" }),
name: varchar("name").notNull(), name: varchar("name").notNull(),
niceId: varchar("niceId").notNull(),
type: varchar("type") type: varchar("type")
.$type< .$type<
| "openai" | "openai"
@@ -1729,7 +1732,9 @@ export const aiProviders = pgTable("aiProviders", {
enabled: boolean("enabled").notNull().default(true), enabled: boolean("enabled").notNull().default(true),
createdAt: bigint("createdAt", { mode: "number" }).notNull(), createdAt: bigint("createdAt", { mode: "number" }).notNull(),
updatedAt: bigint("updatedAt", { mode: "number" }).notNull() updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
}); },
(t) => [index("idx_aiProviders_orgId_niceId").on(t.orgId, t.niceId)]
);
export const aiModels = pgTable( export const aiModels = pgTable(
"aiModels", "aiModels",
+10 -3
View File
@@ -1668,12 +1668,15 @@ export const statusHistory = sqliteTable(
] ]
); );
export const aiProviders = sqliteTable("aiProviders", { export const aiProviders = sqliteTable(
"aiProviders",
{
providerId: integer("providerId").primaryKey({ autoIncrement: true }), providerId: integer("providerId").primaryKey({ autoIncrement: true }),
orgId: text("orgId") orgId: text("orgId")
.notNull() .notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }), .references(() => orgs.orgId, { onDelete: "cascade" }),
name: text("name").notNull(), name: text("name").notNull(),
niceId: text("niceId").notNull(),
type: text("type") type: text("type")
.$type< .$type<
| "openai" | "openai"
@@ -1710,10 +1713,14 @@ export const aiProviders = sqliteTable("aiProviders", {
skipTlsVerification: integer("skipTlsVerification", { mode: "boolean" }) skipTlsVerification: integer("skipTlsVerification", { mode: "boolean" })
.notNull() .notNull()
.default(false), .default(false),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), enabled: integer("enabled", { mode: "boolean" })
.notNull()
.default(true),
createdAt: integer("createdAt").notNull(), createdAt: integer("createdAt").notNull(),
updatedAt: integer("updatedAt").notNull() updatedAt: integer("updatedAt").notNull()
}); },
(t) => [index("idx_aiProviders_orgId_niceId").on(t.orgId, t.niceId)]
);
export const aiModels = sqliteTable( export const aiModels = sqliteTable(
"aiModels", "aiModels",
@@ -25,6 +25,7 @@ import {
refineProviderUpstreamFields refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation"; } from "@server/routers/aiProvider/validation";
import { serializeCapabilities } from "@server/lib/aiCapabilities"; import { serializeCapabilities } from "@server/lib/aiCapabilities";
import { getUniqueProviderName, getUniqueResourceName } from "@server/db/names";
const paramsSchema = z.strictObject({ const paramsSchema = z.strictObject({
orgId: z.string().nonempty() orgId: z.string().nonempty()
@@ -133,11 +134,14 @@ export async function createAiProvider(
); );
} }
const niceId = await getUniqueProviderName(orgId);
const [provider] = await db const [provider] = await db
.insert(aiProviders) .insert(aiProviders)
.values({ .values({
orgId, orgId,
name, name,
niceId,
type, type,
upstreamUrl: resolved.upstreamUrl, upstreamUrl: resolved.upstreamUrl,
apiKey: encryptedApiKey, apiKey: encryptedApiKey,
+36 -1
View File
@@ -7,7 +7,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger"; import logger from "@server/logger";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { eq } from "drizzle-orm"; import { eq, ne, and } from "drizzle-orm";
import { encrypt } from "@server/lib/crypto"; import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config"; import config from "@server/lib/config";
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types"; import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
@@ -37,6 +37,15 @@ const paramsSchema = z.strictObject({
const bodySchema = z.strictObject({ const bodySchema = z.strictObject({
name: z.string().nonempty().optional(), name: z.string().nonempty().optional(),
niceId: z
.string()
.min(1)
.max(255)
.regex(
/^[a-zA-Z0-9-]+$/,
"niceId can only contain letters, numbers, and dashes"
)
.optional(),
upstreamUrl: z.url().optional().nullable(), upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(), apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional(), authType: aiAuthTypeSchema.optional(),
@@ -170,6 +179,9 @@ export async function updateAiProvider(
if (body.name !== undefined) { if (body.name !== undefined) {
updateData.name = body.name; updateData.name = body.name;
} }
if (body.niceId !== undefined) {
updateData.niceId = body.niceId;
}
if (body.skipTlsVerification !== undefined) { if (body.skipTlsVerification !== undefined) {
updateData.skipTlsVerification = body.skipTlsVerification; updateData.skipTlsVerification = body.skipTlsVerification;
} }
@@ -199,6 +211,29 @@ export async function updateAiProvider(
updateData.headers = serializeAiProviderHeaders(body.headers, key); updateData.headers = serializeAiProviderHeaders(body.headers, key);
} }
if (updateData.niceId) {
const [existingAiProvider] = await db
.select()
.from(aiProviders)
.where(
and(
eq(aiProviders.niceId, updateData.niceId),
eq(aiProviders.orgId, existing.orgId),
ne(aiProviders.providerId, existing.providerId) // exclude the current provider from the search
)
)
.limit(1);
if (existingAiProvider) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A resource with niceId "${updateData.niceId}" already exists`
)
);
}
}
const [provider] = await db const [provider] = await db
.update(aiProviders) .update(aiProviders)
.set(updateData) .set(updateData)