Allow overlapping domains on public inference resources

This commit is contained in:
Owen
2026-08-14 11:31:56 -04:00
parent c568251d8d
commit 4989d1e31a
4 changed files with 41 additions and 6 deletions
+11
View File
@@ -306,6 +306,7 @@ export async function updatePublicResources(
existingResource.resourceId, existingResource.resourceId,
resourceData["full-domain"]!, resourceData["full-domain"]!,
orgId, orgId,
resourceData.mode === "inference",
trx trx
); );
@@ -1098,6 +1099,7 @@ export async function updatePublicResources(
undefined, undefined,
resourceData["full-domain"]!, resourceData["full-domain"]!,
orgId, orgId,
resourceData.mode === "inference",
trx trx
); );
@@ -2113,6 +2115,7 @@ export async function getDomain(
resourceId: number | undefined, resourceId: number | undefined,
fullDomain: string, fullDomain: string,
orgId: string, orgId: string,
isInference: boolean,
trx: Transaction trx: Transaction
) { ) {
const [fullDomainExists] = await trx const [fullDomainExists] = await trx
@@ -2122,6 +2125,14 @@ export async function getDomain(
and( and(
eq(resources.fullDomain, fullDomain), eq(resources.fullDomain, fullDomain),
eq(resources.orgId, orgId), eq(resources.orgId, orgId),
// Inference resources route through the central AI gateway
// rather than normal target-based proxying, so they're
// allowed to share a full-domain with a non-inference
// resource (and vice versa) - only conflicts within the
// same routing category are rejected.
isInference
? eq(resources.mode, "inference")
: ne(resources.mode, "inference"),
resourceId resourceId
? ne(resources.resourceId, resourceId) ? ne(resources.resourceId, resourceId)
: isNotNull(resources.resourceId) : isNotNull(resources.resourceId)
+14 -3
View File
@@ -18,7 +18,7 @@ import {
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { eq, and } from "drizzle-orm"; import { eq, and, ne } from "drizzle-orm";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import logger from "@server/logger"; import logger from "@server/logger";
import { subdomainSchema, wildcardSubdomainSchema } from "@server/lib/schemas"; import { subdomainSchema, wildcardSubdomainSchema } from "@server/lib/schemas";
@@ -484,11 +484,22 @@ async function createHttpResource(
logger.debug(`Full domain: ${fullDomain}`); logger.debug(`Full domain: ${fullDomain}`);
// make sure the full domain is unique // make sure the full domain is unique. Inference resources are routed
// through the central AI gateway rather than normal target-based
// proxying, so they're allowed to share a full-domain with a
// non-inference resource (and vice versa) - only conflicts within the
// same routing category are rejected.
const existingResource = await db const existingResource = await db
.select() .select()
.from(resources) .from(resources)
.where(eq(resources.fullDomain, fullDomain)); .where(
and(
eq(resources.fullDomain, fullDomain),
effectiveMode === "inference"
? eq(resources.mode, "inference")
: ne(resources.mode, "inference")
)
);
if (existingResource.length > 0) { if (existingResource.length > 0) {
return next( return next(
+14 -1
View File
@@ -596,10 +596,23 @@ async function updateHttpResource(
logger.debug(`Full domain: ${fullDomain}`); logger.debug(`Full domain: ${fullDomain}`);
if (fullDomain) { if (fullDomain) {
// Inference resources route through the central AI gateway
// rather than normal target-based proxying, so they're allowed
// to share a full-domain with a non-inference resource (and
// vice versa) - only conflicts within the same routing category
// are rejected. mode isn't updatable here, so `resource.mode`
// reflects the resource's actual (unchanging) routing category.
const [existingDomain] = await db const [existingDomain] = await db
.select() .select()
.from(resources) .from(resources)
.where(eq(resources.fullDomain, fullDomain)); .where(
and(
eq(resources.fullDomain, fullDomain),
resource.mode === "inference"
? eq(resources.mode, "inference")
: ne(resources.mode, "inference")
)
);
if ( if (
existingDomain && existingDomain &&
@@ -55,12 +55,12 @@ const listAllSiteResourcesByOrgQuerySchema = z.strictObject({
}), }),
query: z.string().optional(), query: z.string().optional(),
mode: z mode: z
.enum(["host", "cidr", "http"]) .enum(["host", "cidr", "http", "ssh", "inference"])
.optional() .optional()
.catch(undefined) .catch(undefined)
.openapi({ .openapi({
type: "string", type: "string",
enum: ["host", "cidr", "http"], enum: ["host", "cidr", "http", "ssh", "inference"],
description: "Filter site resources by mode" description: "Filter site resources by mode"
}), }),
sort_by: z sort_by: z