mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-22 13:59:05 +02:00
Merge upstream/dev into feature-response-headers
Resolve conflicts against upstream's refactors: - server/db/sqlite/schema/schema.ts: adopt upstream's reindented sqliteTable(name, cols, indexes) form for sites/resources, re-applying the headers -> requestHeaders/responseHeaders split. Kept in sync with the Postgres schema. - server/lib/traefik/headersMiddleware.ts: extend upstream's extracted buildCustomHeadersMiddleware helper to take requestHeaders and responseHeaders and emit both customRequestHeaders and customResponseHeaders. - server/lib/traefik/getTraefikConfig.ts and server/private/lib/traefik/getTraefikConfig.ts: keep upstream's helper extraction and appendPathMatch refactor, dropping the superseded inline blocks. Also carry the feature forward onto code that moved upstream: - The resource settings UI moved from resources/proxy/[niceId]/proxy to resources/public/[niceId]/http, which dropped this branch's changes in the previous merge. Re-add the request/response header inputs there and rename the vestigial headers field on the tcp page. - messages/da-DK.json is new upstream and still had the old customHeaders key; rename it in line with the other locales. Per the contributing docs, versioned migrations are intentionally omitted so maintainers can write them at release time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { join } from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import {
|
||||
aiProviders,
|
||||
clients,
|
||||
db,
|
||||
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(
|
||||
orgId: string
|
||||
): Promise<string> {
|
||||
|
||||
+24
-11
@@ -1,3 +1,4 @@
|
||||
import config from "@server/lib/config";
|
||||
import { Pool, PoolConfig } from "pg";
|
||||
|
||||
export function createPoolConfig(
|
||||
@@ -18,17 +19,7 @@ export function createPoolConfig(
|
||||
keepAliveInitialDelayMillis: 10000, // send first keepalive after 10s of idle
|
||||
// Allow connections to be released and recreated more aggressively
|
||||
// to avoid stale connections building up
|
||||
allowExitOnIdle: false,
|
||||
// Disable JIT compilation for this connection. Our hot-path queries
|
||||
// (e.g. resource-by-domain lookups) join many tables but only ever
|
||||
// return a handful of rows. When planner row estimates drift (e.g.
|
||||
// due to autovacuum lag under write-heavy load), Postgres decides
|
||||
// these plans are expensive enough to JIT-compile, which can add
|
||||
// multiple seconds of pure compilation overhead per query and
|
||||
// saturate the connection pool. JIT never pays off for these
|
||||
// short-lived OLTP queries, so it's disabled outright rather than
|
||||
// relying on statistics staying fresh.
|
||||
options: "-c jit=off"
|
||||
allowExitOnIdle: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,6 +40,28 @@ export function attachPoolErrorHandlers(pool: Pool, label: string): void {
|
||||
`Failed to set statement_timeout on ${label} client: ${err.message}`
|
||||
);
|
||||
});
|
||||
|
||||
// Disable JIT compilation for this connection. Our hot-path queries
|
||||
// (e.g. resource-by-domain lookups) join many tables but only ever
|
||||
// return a handful of rows. When planner row estimates drift (e.g.
|
||||
// due to autovacuum lag under write-heavy load), Postgres decides
|
||||
// these plans are expensive enough to JIT-compile, which can add
|
||||
// multiple seconds of pure compilation overhead per query and
|
||||
// saturate the connection pool. JIT never pays off for these
|
||||
// short-lived OLTP queries, so it's disabled outright rather than
|
||||
// relying on statistics staying fresh.
|
||||
//
|
||||
// Set via a runtime SET command rather than the `options: "-c
|
||||
// jit=off"` startup parameter: connections in SaaS mode go through
|
||||
// a pooler (e.g. PgBouncer) that rejects arbitrary startup packet
|
||||
// options with a protocol_violation (08P01) error.
|
||||
if (config.getRawConfig().postgres?.pool.jit_mode == false) {
|
||||
client.query("SET jit = off").catch((err: Error) => {
|
||||
console.warn(
|
||||
`Failed to set jit=off on ${label} client: ${err.message}`
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -29,25 +29,6 @@ import {
|
||||
labels
|
||||
} from "./schema";
|
||||
|
||||
export const certificates = pgTable("certificates", {
|
||||
certId: serial("certId").primaryKey(),
|
||||
domain: varchar("domain", { length: 255 }).notNull().unique(),
|
||||
domainId: varchar("domainId").references(() => domains.domainId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
wildcard: boolean("wildcard").default(false),
|
||||
status: varchar("status", { length: 50 }).notNull().default("pending"), // pending, requested, valid, expired, failed
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }),
|
||||
lastRenewalAttempt: bigint("lastRenewalAttempt", { mode: "number" }),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull(),
|
||||
orderId: varchar("orderId", { length: 500 }),
|
||||
errorMessage: text("errorMessage"),
|
||||
renewalCount: integer("renewalCount").default(0),
|
||||
certFile: text("certFile"),
|
||||
keyFile: text("keyFile")
|
||||
});
|
||||
|
||||
export const dnsChallenge = pgTable("dnsChallenges", {
|
||||
dnsChallengeId: serial("dnsChallengeId").primaryKey(),
|
||||
domain: varchar("domain", { length: 255 }).notNull(),
|
||||
@@ -95,7 +76,8 @@ export const subscriptions = pgTable("subscriptions", {
|
||||
billingCycleAnchor: bigint("billingCycleAnchor", { mode: "number" }),
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }),
|
||||
trial: boolean("trial").default(false),
|
||||
type: varchar("type", { length: 50 }) // tier1, tier2, tier3, or license
|
||||
type: varchar("type", { length: 50 }), // tier1, tier2, tier3, or license
|
||||
override: boolean("override").default(false)
|
||||
});
|
||||
|
||||
export const subscriptionItems = pgTable("subscriptionItems", {
|
||||
@@ -486,6 +468,9 @@ export const eventStreamingDestinations = pgTable(
|
||||
sendRequestLogs: boolean("sendRequestLogs").notNull().default(false),
|
||||
sendActionLogs: boolean("sendActionLogs").notNull().default(false),
|
||||
sendAccessLogs: boolean("sendAccessLogs").notNull().default(false),
|
||||
sendAISessionLogs: boolean("sendAISessionLogs")
|
||||
.notNull()
|
||||
.default(false),
|
||||
type: varchar("type", { length: 50 }).notNull(), // e.g. "http", "kafka", etc.
|
||||
config: text("config").notNull(), // JSON string with the configuration for the destination
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
@@ -632,7 +617,6 @@ export const trialNotifications = pgTable("trialNotifications", {
|
||||
export type Approval = InferSelectModel<typeof approvals>;
|
||||
export type Limit = InferSelectModel<typeof limits>;
|
||||
export type Account = InferSelectModel<typeof account>;
|
||||
export type Certificate = InferSelectModel<typeof certificates>;
|
||||
export type DnsChallenge = InferSelectModel<typeof dnsChallenge>;
|
||||
export type Customer = InferSelectModel<typeof customers>;
|
||||
export type Subscription = InferSelectModel<typeof subscriptions>;
|
||||
|
||||
+626
-61
@@ -3,6 +3,7 @@ import { InferSelectModel, sql } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
boolean,
|
||||
check,
|
||||
index,
|
||||
integer,
|
||||
pgTable,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
serial,
|
||||
text,
|
||||
unique,
|
||||
uniqueIndex,
|
||||
varchar
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
@@ -18,7 +20,7 @@ export const domains = pgTable("domains", {
|
||||
domainId: varchar("domainId").primaryKey(),
|
||||
baseDomain: varchar("baseDomain").notNull(),
|
||||
configManaged: boolean("configManaged").notNull().default(false),
|
||||
type: varchar("type"), // "ns", "cname", "wildcard"
|
||||
type: varchar("type").$type<"ns" | "cname" | "wildcard">(),
|
||||
verified: boolean("verified").notNull().default(false),
|
||||
failed: boolean("failed").notNull().default(false),
|
||||
tries: integer("tries").notNull().default(0),
|
||||
@@ -63,6 +65,11 @@ export const orgs = pgTable("orgs", {
|
||||
) // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
|
||||
.notNull()
|
||||
.default(0),
|
||||
settingsLogRetentionDaysAISessions: integer(
|
||||
"settingsLogRetentionDaysAISessions"
|
||||
) // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
|
||||
.notNull()
|
||||
.default(0),
|
||||
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
|
||||
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
|
||||
isBillingOrg: boolean("isBillingOrg"),
|
||||
@@ -98,7 +105,7 @@ export const sites = pgTable(
|
||||
}),
|
||||
name: varchar("name").notNull(),
|
||||
pubKey: varchar("pubKey"),
|
||||
subnet: varchar("subnet"),
|
||||
exitNodeSubnet: text("exitNodeSubnet"), // this is the subnet when connecting to an exit node and INCLUDES THE CIDR
|
||||
megabytesIn: real("bytesIn").default(0),
|
||||
megabytesOut: real("bytesOut").default(0),
|
||||
lastBandwidthUpdate: varchar("lastBandwidthUpdate"),
|
||||
@@ -107,6 +114,7 @@ export const sites = pgTable(
|
||||
lastPing: integer("lastPing"),
|
||||
address: varchar("address"),
|
||||
endpoint: varchar("endpoint"),
|
||||
localEndpoints: varchar("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
|
||||
publicKey: varchar("publicKey"),
|
||||
lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
|
||||
listenPort: integer("listenPort"),
|
||||
@@ -194,14 +202,22 @@ export const resources = pgTable(
|
||||
postAuthPath: text("postAuthPath"),
|
||||
health: varchar("health").default("unknown"), // "healthy", "unhealthy", "unknown"
|
||||
wildcard: boolean("wildcard").notNull().default(false),
|
||||
mode: text("mode").default("http").notNull(), // rdp, ssh, http, vnc
|
||||
mode: text("mode")
|
||||
.default("http")
|
||||
.$type<
|
||||
"rdp" | "ssh" | "http" | "vnc" | "inference" | "tcp" | "udp"
|
||||
>()
|
||||
.notNull(),
|
||||
pamMode: varchar("pamMode", { length: 32 })
|
||||
.$type<"passthrough" | "push">()
|
||||
.default("passthrough"),
|
||||
authDaemonMode: varchar("authDaemonMode", { length: 32 })
|
||||
.$type<"site" | "remote" | "native">()
|
||||
.default("site"),
|
||||
authDaemonPort: integer("authDaemonPort").default(22123)
|
||||
authDaemonPort: integer("authDaemonPort").default(22123),
|
||||
status: varchar("status")
|
||||
.$type<"pending" | "approved">()
|
||||
.default("approved")
|
||||
},
|
||||
(t) => [
|
||||
index("idx_resources_fulldomain")
|
||||
@@ -212,16 +228,55 @@ export const resources = pgTable(
|
||||
]
|
||||
);
|
||||
|
||||
export const labels = pgTable("labels", {
|
||||
labelId: serial("labelId").primaryKey(),
|
||||
name: varchar("name").notNull(),
|
||||
color: varchar("color").notNull(),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
});
|
||||
export const resourceAiProviders = pgTable(
|
||||
"resourceAiProviders",
|
||||
{
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
providerId: integer("providerId")
|
||||
.notNull()
|
||||
.references(() => aiProviders.providerId, { onDelete: "cascade" }),
|
||||
accessMode: varchar("accessMode")
|
||||
.$type<"inherit" | "select">()
|
||||
.notNull()
|
||||
.default("inherit"),
|
||||
enabled: boolean("enabled").notNull().default(true)
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.resourceId, t.providerId] })]
|
||||
);
|
||||
|
||||
export const resourceAiModels = pgTable(
|
||||
"resourceAiModels",
|
||||
{
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
modelId: integer("modelId")
|
||||
.notNull()
|
||||
.references(() => aiModels.modelId, { onDelete: "cascade" }),
|
||||
listType: varchar("listType")
|
||||
.$type<"allow" | "block">()
|
||||
.notNull()
|
||||
.default("allow")
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.resourceId, t.modelId] })]
|
||||
);
|
||||
|
||||
export const labels = pgTable(
|
||||
"labels",
|
||||
{
|
||||
labelId: serial("labelId").primaryKey(),
|
||||
name: varchar("name").notNull(),
|
||||
color: varchar("color").notNull(),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull()
|
||||
},
|
||||
(t) => [index("idx_labels_orgid").on(t.orgId)]
|
||||
);
|
||||
|
||||
export const launcherViews = pgTable("launcherViews", {
|
||||
viewId: serial("viewId").primaryKey(),
|
||||
@@ -314,11 +369,18 @@ export const targets = pgTable(
|
||||
"targets",
|
||||
{
|
||||
targetId: serial("targetId").primaryKey(),
|
||||
resourceId: integer("resourceId")
|
||||
.references(() => resources.resourceId, {
|
||||
resourceId: integer("resourceId").references(
|
||||
() => resources.resourceId,
|
||||
{
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
}
|
||||
),
|
||||
providerId: integer("providerId").references(
|
||||
() => aiProviders.providerId,
|
||||
{
|
||||
onDelete: "cascade"
|
||||
}
|
||||
),
|
||||
siteId: integer("siteId")
|
||||
.references(() => sites.siteId, {
|
||||
onDelete: "cascade"
|
||||
@@ -342,6 +404,7 @@ export const targets = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
index("idx_targets_resourceid_siteid").on(t.resourceId, t.siteId),
|
||||
index("idx_targets_providerid_siteid").on(t.providerId, t.siteId),
|
||||
index("idx_targets_site_enabled_priority_target_resource")
|
||||
.on(t.siteId, t.priority.desc(), t.targetId, t.resourceId)
|
||||
.where(sql`${t.enabled} = true`)
|
||||
@@ -421,11 +484,14 @@ export const siteResources = pgTable(
|
||||
onDelete: "restrict"
|
||||
}
|
||||
),
|
||||
requiresExitNodeConnection: boolean("requiresExitNodeConnection")
|
||||
.notNull()
|
||||
.default(false),
|
||||
niceId: varchar("niceId").notNull(),
|
||||
name: varchar("name").notNull(),
|
||||
ssl: boolean("ssl").notNull().default(false),
|
||||
mode: varchar("mode")
|
||||
.$type<"host" | "cidr" | "http" | "ssh">()
|
||||
.$type<"host" | "cidr" | "http" | "ssh" | "inference">()
|
||||
.notNull(), // "host" | "cidr" | "http"
|
||||
scheme: varchar("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
|
||||
proxyPort: integer("proxyPort"), // only for port mode
|
||||
@@ -452,11 +518,53 @@ export const siteResources = pgTable(
|
||||
onDelete: "set null"
|
||||
}),
|
||||
subdomain: varchar("subdomain"),
|
||||
fullDomain: varchar("fullDomain")
|
||||
fullDomain: varchar("fullDomain"),
|
||||
status: varchar("status")
|
||||
.$type<"pending" | "approved">()
|
||||
.default("approved")
|
||||
},
|
||||
(t) => [index("idx_siteresources_orgid_niceid").on(t.orgId, t.niceId)]
|
||||
);
|
||||
|
||||
export const siteResourceAiProviders = pgTable(
|
||||
"siteResourceAiProviders",
|
||||
{
|
||||
siteResourceId: integer("siteResourceId")
|
||||
.notNull()
|
||||
.references(() => siteResources.siteResourceId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
providerId: integer("providerId")
|
||||
.notNull()
|
||||
.references(() => aiProviders.providerId, { onDelete: "cascade" }),
|
||||
accessMode: varchar("accessMode")
|
||||
.$type<"inherit" | "select">()
|
||||
.notNull()
|
||||
.default("inherit"),
|
||||
enabled: boolean("enabled").notNull().default(true)
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.siteResourceId, t.providerId] })]
|
||||
);
|
||||
|
||||
export const siteResourceAiModels = pgTable(
|
||||
"siteResourceAiModels",
|
||||
{
|
||||
siteResourceId: integer("siteResourceId")
|
||||
.notNull()
|
||||
.references(() => siteResources.siteResourceId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
modelId: integer("modelId")
|
||||
.notNull()
|
||||
.references(() => aiModels.modelId, { onDelete: "cascade" }),
|
||||
listType: varchar("listType")
|
||||
.$type<"allow" | "block">()
|
||||
.notNull()
|
||||
.default("allow")
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.siteResourceId, t.modelId] })]
|
||||
);
|
||||
|
||||
export const networks = pgTable(
|
||||
"networks",
|
||||
{
|
||||
@@ -590,15 +698,19 @@ export const twoFactorBackupCodes = pgTable("twoFactorBackupCodes", {
|
||||
codeHash: varchar("codeHash").notNull()
|
||||
});
|
||||
|
||||
export const sessions = pgTable("session", {
|
||||
sessionId: varchar("id").primaryKey(),
|
||||
userId: varchar("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }).notNull(),
|
||||
issuedAt: bigint("issuedAt", { mode: "number" }),
|
||||
deviceAuthUsed: boolean("deviceAuthUsed").notNull().default(false)
|
||||
});
|
||||
export const sessions = pgTable(
|
||||
"session",
|
||||
{
|
||||
sessionId: varchar("id").primaryKey(),
|
||||
userId: varchar("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }).notNull(),
|
||||
issuedAt: bigint("issuedAt", { mode: "number" }),
|
||||
deviceAuthUsed: boolean("deviceAuthUsed").notNull().default(false)
|
||||
},
|
||||
(t) => [index("idx_sessions_userid").on(t.userId)]
|
||||
);
|
||||
|
||||
export const newtSessions = pgTable("newtSession", {
|
||||
sessionId: varchar("id").primaryKey(),
|
||||
@@ -608,19 +720,26 @@ export const newtSessions = pgTable("newtSession", {
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
|
||||
});
|
||||
|
||||
export const userOrgs = pgTable("userOrgs", {
|
||||
userId: varchar("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
isOwner: boolean("isOwner").notNull().default(false),
|
||||
autoProvisioned: boolean("autoProvisioned").default(false),
|
||||
pamUsername: varchar("pamUsername") // cleaned username for ssh and such
|
||||
});
|
||||
export const userOrgs = pgTable(
|
||||
"userOrgs",
|
||||
{
|
||||
userId: varchar("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
isOwner: boolean("isOwner").notNull().default(false),
|
||||
autoProvisioned: boolean("autoProvisioned").default(false),
|
||||
pamUsername: varchar("pamUsername") // cleaned username for ssh and such
|
||||
},
|
||||
(t) => [
|
||||
index("idx_userOrgs_userid").on(t.userId),
|
||||
index("idx_userOrgs_orgid").on(t.orgId)
|
||||
]
|
||||
);
|
||||
|
||||
export const emailVerificationCodes = pgTable("emailVerificationCodes", {
|
||||
codeId: serial("id").primaryKey(),
|
||||
@@ -648,22 +767,26 @@ export const actions = pgTable("actions", {
|
||||
description: varchar("description")
|
||||
});
|
||||
|
||||
export const roles = pgTable("roles", {
|
||||
roleId: serial("roleId").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
isAdmin: boolean("isAdmin"),
|
||||
name: varchar("name").notNull(),
|
||||
description: varchar("description"),
|
||||
requireDeviceApproval: boolean("requireDeviceApproval").default(false),
|
||||
sshSudoMode: varchar("sshSudoMode", { length: 32 }).default("none"), // "none" | "full" | "commands"
|
||||
sshSudoCommands: text("sshSudoCommands").default("[]"),
|
||||
sshCreateHomeDir: boolean("sshCreateHomeDir").default(true),
|
||||
sshUnixGroups: text("sshUnixGroups").default("[]")
|
||||
});
|
||||
export const roles = pgTable(
|
||||
"roles",
|
||||
{
|
||||
roleId: serial("roleId").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
isAdmin: boolean("isAdmin"),
|
||||
name: varchar("name").notNull(),
|
||||
description: varchar("description"),
|
||||
requireDeviceApproval: boolean("requireDeviceApproval").default(false),
|
||||
sshSudoMode: varchar("sshSudoMode", { length: 32 }).default("full"), // "none" | "full" | "commands"
|
||||
sshSudoCommands: text("sshSudoCommands").default("[]"),
|
||||
sshCreateHomeDir: boolean("sshCreateHomeDir").default(true),
|
||||
sshUnixGroups: text("sshUnixGroups").default("[]")
|
||||
},
|
||||
(t) => [index("idx_roles_orgid").on(t.orgId)]
|
||||
);
|
||||
|
||||
export const userOrgRoles = pgTable(
|
||||
"userOrgRoles",
|
||||
@@ -900,12 +1023,16 @@ export const resourceAccessToken = pgTable("resourceAccessToken", {
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
userId: varchar("userId").references(() => users.userId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
path: varchar("path"),
|
||||
tokenHash: varchar("tokenHash").notNull(),
|
||||
sessionLength: bigint("sessionLength", { mode: "number" }).notNull(),
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }),
|
||||
title: varchar("title"),
|
||||
description: varchar("description"),
|
||||
persistSession: boolean("persistSession").notNull().default(false),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull()
|
||||
});
|
||||
|
||||
@@ -1000,7 +1127,17 @@ export const resourceRules = pgTable("resourceRules", {
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
priority: integer("priority").notNull(),
|
||||
action: varchar("action").notNull(), // ACCEPT, DROP, PASS
|
||||
match: varchar("match").notNull(), // CIDR, PATH, IP
|
||||
match: varchar("match")
|
||||
.$type<
|
||||
| "CIDR"
|
||||
| "PATH"
|
||||
| "IP"
|
||||
| "COUNTRY"
|
||||
| "COUNTRY_IS_NOT"
|
||||
| "ASN"
|
||||
| "REGION"
|
||||
>()
|
||||
.notNull(), // CIDR, PATH, IP
|
||||
value: varchar("value").notNull()
|
||||
});
|
||||
|
||||
@@ -1015,7 +1152,15 @@ export const resourcePolicyRules = pgTable("resourcePolicyRules", {
|
||||
priority: integer("priority").notNull(),
|
||||
action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
|
||||
match: varchar("match")
|
||||
.$type<"CIDR" | "PATH" | "IP" | "COUNTRY" | "ASN" | "REGION">()
|
||||
.$type<
|
||||
| "CIDR"
|
||||
| "PATH"
|
||||
| "IP"
|
||||
| "COUNTRY"
|
||||
| "COUNTRY_IS_NOT"
|
||||
| "ASN"
|
||||
| "REGION"
|
||||
>()
|
||||
.notNull(),
|
||||
value: varchar("value").notNull()
|
||||
});
|
||||
@@ -1122,6 +1267,52 @@ export const apiKeyOrg = pgTable("apiKeyOrg", {
|
||||
.notNull()
|
||||
});
|
||||
|
||||
export const virtualApiKeys = pgTable(
|
||||
"virtualApiKeys",
|
||||
{
|
||||
virtualApiKeyId: varchar("virtualApiKeyId").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
kind: varchar("kind").$type<"user" | "manual">().notNull(),
|
||||
userId: varchar("userId").references(() => users.userId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
name: varchar("name"),
|
||||
description: varchar("description"),
|
||||
token: varchar("token").notNull(),
|
||||
lastChars: varchar("lastChars").notNull(),
|
||||
allResources: boolean("allResources").notNull().default(false),
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }),
|
||||
lastUsedAt: bigint("lastUsedAt", { mode: "number" }),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
createdByUserId: varchar("createdByUserId").references(
|
||||
() => users.userId,
|
||||
{ onDelete: "set null" }
|
||||
)
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("virtual_api_key_user_identity_uniq")
|
||||
.on(t.orgId, t.userId)
|
||||
.where(sql`${t.kind} = 'user'`)
|
||||
]
|
||||
);
|
||||
|
||||
export const virtualApiKeyResources = pgTable(
|
||||
"virtualApiKeyResources",
|
||||
{
|
||||
virtualApiKeyId: varchar("virtualApiKeyId")
|
||||
.notNull()
|
||||
.references(() => virtualApiKeys.virtualApiKeyId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.virtualApiKeyId, t.resourceId] })]
|
||||
);
|
||||
|
||||
export const idpOrg = pgTable("idpOrg", {
|
||||
idpId: integer("idpId")
|
||||
.notNull()
|
||||
@@ -1154,6 +1345,7 @@ export const clients = pgTable(
|
||||
name: varchar("name").notNull(),
|
||||
pubKey: varchar("pubKey"),
|
||||
subnet: varchar("subnet").notNull(),
|
||||
exitNodeSubnet: varchar("exitNodeSubnet"), // INCLUDES THE CIDR
|
||||
megabytesIn: real("bytesIn"),
|
||||
megabytesOut: real("bytesOut"),
|
||||
lastBandwidthUpdate: varchar("lastBandwidthUpdate"),
|
||||
@@ -1237,7 +1429,10 @@ export const olms = pgTable(
|
||||
}),
|
||||
archived: boolean("archived").notNull().default(false)
|
||||
},
|
||||
(t) => [index("idx_olms_clientid").on(t.clientId)]
|
||||
(t) => [
|
||||
index("idx_olms_clientid").on(t.clientId),
|
||||
index("idx_olms_userid").on(t.userId)
|
||||
]
|
||||
);
|
||||
|
||||
export const currentFingerprint = pgTable("currentFingerprint", {
|
||||
@@ -1512,6 +1707,358 @@ export const statusHistory = pgTable(
|
||||
]
|
||||
);
|
||||
|
||||
export const aiProviders = pgTable(
|
||||
"aiProviders",
|
||||
{
|
||||
providerId: serial("providerId").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
name: varchar("name").notNull(),
|
||||
niceId: varchar("niceId").notNull(),
|
||||
type: varchar("type")
|
||||
.$type<
|
||||
| "openai"
|
||||
| "anthropic"
|
||||
| "googleGemini"
|
||||
| "vertexAi"
|
||||
| "bedrock"
|
||||
| "microsoftFoundry"
|
||||
| "openRouter"
|
||||
| "vercelAiGateway"
|
||||
| "custom"
|
||||
>()
|
||||
.notNull(),
|
||||
upstreamUrl: text("upstreamUrl"),
|
||||
apiKey: text("apiKey"),
|
||||
apiKeyLastChars: varchar("apiKeyLastChars"),
|
||||
authType: varchar("authType")
|
||||
.$type<
|
||||
| "bearer"
|
||||
| "x-api-key"
|
||||
| "x-goog-api-key"
|
||||
| "hec"
|
||||
| "cf-aig-authorization"
|
||||
| "none"
|
||||
| "passthrough"
|
||||
>()
|
||||
.notNull(),
|
||||
routingMode: varchar("routingMode")
|
||||
.$type<"url" | "target">()
|
||||
.notNull()
|
||||
.default("url"),
|
||||
capabilities: text("capabilities").notNull().default("[]"),
|
||||
headers: text("headers"), // JSON array of { name, value }
|
||||
skipTlsVerification: boolean("skipTlsVerification")
|
||||
.notNull()
|
||||
.default(false),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
||||
},
|
||||
(t) => [index("idx_aiProviders_orgId_niceId").on(t.orgId, t.niceId)]
|
||||
);
|
||||
|
||||
export const aiModels = pgTable(
|
||||
"aiModels",
|
||||
{
|
||||
modelId: serial("modelId").primaryKey(),
|
||||
providerId: integer("providerId")
|
||||
.notNull()
|
||||
.references(() => aiProviders.providerId, { onDelete: "cascade" }),
|
||||
modelKey: varchar("modelKey").notNull(),
|
||||
name: varchar("name").notNull(),
|
||||
listType: varchar("listType")
|
||||
.$type<"allow" | "block">()
|
||||
.notNull()
|
||||
.default("allow"),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
||||
},
|
||||
(t) => [unique("ai_model_provider_key_uniq").on(t.providerId, t.modelKey)]
|
||||
);
|
||||
|
||||
export const aiBudgets = pgTable(
|
||||
"aiBudgets",
|
||||
{
|
||||
budgetId: serial("budgetId").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
providerId: integer("providerId").references(
|
||||
() => aiProviders.providerId,
|
||||
{ onDelete: "cascade" }
|
||||
),
|
||||
modelId: integer("modelId").references(() => aiModels.modelId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
resourceId: integer("resourceId").references(
|
||||
() => resources.resourceId,
|
||||
{ onDelete: "cascade" }
|
||||
),
|
||||
siteResourceId: integer("siteResourceId").references(
|
||||
() => siteResources.siteResourceId,
|
||||
{ onDelete: "cascade" }
|
||||
),
|
||||
roleId: integer("roleId").references(() => roles.roleId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
virtualApiKeyId: varchar("virtualApiKeyId").references(
|
||||
() => virtualApiKeys.virtualApiKeyId,
|
||||
{ onDelete: "cascade" }
|
||||
),
|
||||
amount: real("amount").notNull(),
|
||||
unit: varchar("unit").$type<"usd" | "tokens">().notNull(),
|
||||
period: varchar("period")
|
||||
.$type<
|
||||
| "monthly"
|
||||
| "yearly"
|
||||
| "lifetime"
|
||||
| "daily"
|
||||
| "hourly"
|
||||
| "weekly"
|
||||
>()
|
||||
.notNull()
|
||||
.default("monthly"),
|
||||
enforcement: varchar("enforcement")
|
||||
.$type<"hard" | "soft">()
|
||||
.notNull()
|
||||
.default("hard"),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
||||
},
|
||||
(t) => [
|
||||
unique("ai_budget_provider_uniq").on(t.providerId, t.unit, t.period),
|
||||
unique("ai_budget_model_uniq").on(t.modelId, t.unit, t.period),
|
||||
unique("ai_budget_resource_uniq").on(t.resourceId, t.unit, t.period),
|
||||
unique("ai_budget_site_resource_uniq").on(
|
||||
t.siteResourceId,
|
||||
t.unit,
|
||||
t.period
|
||||
),
|
||||
unique("ai_budget_role_uniq").on(t.roleId, t.unit, t.period),
|
||||
unique("ai_budget_virtual_api_key_uniq").on(
|
||||
t.virtualApiKeyId,
|
||||
t.unit,
|
||||
t.period
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export const aiUsageRecords = pgTable(
|
||||
"aiUsageRecords",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
providerId: integer("providerId").references(
|
||||
() => aiProviders.providerId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
resourceId: integer("resourceId").references(
|
||||
() => resources.resourceId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
siteResourceId: integer("siteResourceId").references(
|
||||
() => siteResources.siteResourceId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
userId: varchar("userId").references(() => users.userId, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
virtualApiKeyId: varchar("virtualApiKeyId").references(
|
||||
() => virtualApiKeys.virtualApiKeyId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
// Links this usage record back to the aiSessionLog row for the same
|
||||
// request (aiSessionLog.sessionId), so token/cost usage can be shown
|
||||
// alongside the session transcript. Not a DB-level FK - aiSessionLog
|
||||
// lives in the separate logs database. Nullable because the session
|
||||
// log may be disabled (retention set to 0) while usage tracking
|
||||
// stays on.
|
||||
sessionId: varchar("sessionId"),
|
||||
requestedModel: varchar("requestedModel").notNull(),
|
||||
promptTokens: integer("promptTokens").notNull().default(0),
|
||||
cacheReadTokens: integer("cacheReadTokens").notNull().default(0),
|
||||
cacheWriteTokens: integer("cacheWriteTokens").notNull().default(0),
|
||||
completionTokens: integer("completionTokens").notNull().default(0),
|
||||
reasoningTokens: integer("reasoningTokens").notNull().default(0),
|
||||
totalTokens: integer("totalTokens").notNull().default(0),
|
||||
costUsd: real("costUsd"),
|
||||
estimated: boolean("estimated").notNull().default(false),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull()
|
||||
},
|
||||
(t) => [
|
||||
index("idx_ai_usage_records_org_provider_created").on(
|
||||
t.orgId,
|
||||
t.providerId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_usage_records_org_resource_created").on(
|
||||
t.orgId,
|
||||
t.resourceId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_usage_records_org_site_resource_created").on(
|
||||
t.orgId,
|
||||
t.siteResourceId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_usage_records_org_user_created").on(
|
||||
t.orgId,
|
||||
t.userId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_usage_records_org_virtual_api_key_created").on(
|
||||
t.orgId,
|
||||
t.virtualApiKeyId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_usage_records_session").on(t.sessionId)
|
||||
]
|
||||
);
|
||||
|
||||
export const aiBudgetBreachEvents = pgTable(
|
||||
"aiBudgetBreachEvents",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
orgId: varchar("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
budgetId: integer("budgetId")
|
||||
.notNull()
|
||||
.references(() => aiBudgets.budgetId, { onDelete: "cascade" }),
|
||||
enforcement: varchar("enforcement").$type<"hard" | "soft">().notNull(),
|
||||
unit: varchar("unit").$type<"usd" | "tokens">().notNull(),
|
||||
period: varchar("period")
|
||||
.$type<
|
||||
| "monthly"
|
||||
| "yearly"
|
||||
| "lifetime"
|
||||
| "daily"
|
||||
| "hourly"
|
||||
| "weekly"
|
||||
>()
|
||||
.notNull(),
|
||||
amount: real("amount").notNull(),
|
||||
usageAmount: real("usageAmount").notNull(),
|
||||
blocked: boolean("blocked").notNull(),
|
||||
requestUserId: varchar("requestUserId").references(() => users.userId, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull()
|
||||
},
|
||||
(t) => [
|
||||
index("idx_ai_budget_breach_events_budget_created").on(
|
||||
t.budgetId,
|
||||
t.createdAt
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// Logs the aggregated prompt + response for a single AI gateway request, for
|
||||
// session replay. One row per request (not per streaming chunk). `sessionId`
|
||||
// is a fresh random id per row for now - no cross-request correlation yet,
|
||||
// but the column exists so a future pass can link multiple rows into a real
|
||||
// multi-turn session.
|
||||
export const aiSessionLog = pgTable(
|
||||
"aiSessionLog",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
sessionId: varchar("sessionId").notNull(),
|
||||
orgId: varchar("orgId").references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
providerId: integer("providerId").references(
|
||||
() => aiProviders.providerId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
capability: varchar("capability").notNull(),
|
||||
resourceId: integer("resourceId").references(
|
||||
() => resources.resourceId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
siteResourceId: integer("siteResourceId").references(
|
||||
() => siteResources.siteResourceId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
userId: varchar("userId").references(() => users.userId, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
virtualApiKeyId: varchar("virtualApiKeyId").references(
|
||||
() => virtualApiKeys.virtualApiKeyId,
|
||||
{ onDelete: "set null" }
|
||||
),
|
||||
requestedModel: varchar("requestedModel"),
|
||||
isStream: boolean("isStream").notNull().default(false),
|
||||
requestBody: text("requestBody"),
|
||||
responseBody: text("responseBody"),
|
||||
// Capability-agnostic message transcript (JSON-encoded
|
||||
// NormalizedAiMessage[] from server/lib/aiMessageNormalization.ts),
|
||||
// computed at write time so search/display never need per-capability
|
||||
// parsing logic. Null when normalization couldn't recognize the
|
||||
// shape - callers fall back to requestBody/responseBody.
|
||||
normalizedRequest: text("normalizedRequest"),
|
||||
normalizedResponse: text("normalizedResponse"),
|
||||
// True if any of the request/response (raw or normalized) fields
|
||||
// were cut short at AI_SESSION_LOG_MAX_BODY_CHARS before storage.
|
||||
truncated: boolean("truncated").notNull().default(false),
|
||||
statusCode: integer("statusCode"),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull() // epoch seconds
|
||||
},
|
||||
(t) => [
|
||||
index("idx_ai_session_log_org_created").on(t.orgId, t.createdAt),
|
||||
index("idx_ai_session_log_org_provider_created").on(
|
||||
t.orgId,
|
||||
t.providerId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_session_log_org_resource_created").on(
|
||||
t.orgId,
|
||||
t.resourceId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_session_log_org_site_resource_created").on(
|
||||
t.orgId,
|
||||
t.siteResourceId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_session_log_org_user_created").on(
|
||||
t.orgId,
|
||||
t.userId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_session_log_org_virtual_api_key_created").on(
|
||||
t.orgId,
|
||||
t.virtualApiKeyId,
|
||||
t.createdAt
|
||||
),
|
||||
index("idx_ai_session_log_session").on(t.sessionId)
|
||||
]
|
||||
);
|
||||
|
||||
export const certificates = pgTable("certificates", {
|
||||
certId: serial("certId").primaryKey(),
|
||||
domain: varchar("domain", { length: 255 }).notNull().unique(),
|
||||
domainId: varchar("domainId").references(() => domains.domainId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
wildcard: boolean("wildcard").default(false),
|
||||
status: varchar("status", { length: 50 }).notNull().default("pending"), // pending, requested, valid, expired, failed
|
||||
expiresAt: bigint("expiresAt", { mode: "number" }),
|
||||
lastRenewalAttempt: bigint("lastRenewalAttempt", { mode: "number" }),
|
||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull(),
|
||||
orderId: varchar("orderId", { length: 500 }),
|
||||
errorMessage: text("errorMessage"),
|
||||
renewalCount: integer("renewalCount").default(0),
|
||||
certFile: text("certFile"),
|
||||
keyFile: text("keyFile")
|
||||
});
|
||||
|
||||
export type Org = InferSelectModel<typeof orgs>;
|
||||
export type User = InferSelectModel<typeof users>;
|
||||
export type Site = InferSelectModel<typeof sites>;
|
||||
@@ -1567,6 +2114,10 @@ export type Idp = InferSelectModel<typeof idp>;
|
||||
export type ApiKey = InferSelectModel<typeof apiKeys>;
|
||||
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
||||
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
||||
export type VirtualApiKey = InferSelectModel<typeof virtualApiKeys>;
|
||||
export type VirtualApiKeyResource = InferSelectModel<
|
||||
typeof virtualApiKeyResources
|
||||
>;
|
||||
export type Client = InferSelectModel<typeof clients>;
|
||||
export type ClientSite = InferSelectModel<typeof clientSitesAssociationsCache>;
|
||||
export type Olm = InferSelectModel<typeof olms>;
|
||||
@@ -1595,3 +2146,17 @@ export type LauncherView = InferSelectModel<typeof launcherViews>;
|
||||
export type ResourcePolicy = InferSelectModel<typeof resourcePolicies>;
|
||||
export type RolePolicy = InferSelectModel<typeof rolePolicies>;
|
||||
export type UserPolicy = InferSelectModel<typeof userPolicies>;
|
||||
export type ResourcePolicyRule = InferSelectModel<typeof resourcePolicyRules>;
|
||||
export type AiProvider = InferSelectModel<typeof aiProviders>;
|
||||
export type AiModel = InferSelectModel<typeof aiModels>;
|
||||
export type AiBudget = InferSelectModel<typeof aiBudgets>;
|
||||
export type AiUsageRecord = InferSelectModel<typeof aiUsageRecords>;
|
||||
export type AiBudgetBreachEvent = InferSelectModel<typeof aiBudgetBreachEvents>;
|
||||
export type AiSessionLog = InferSelectModel<typeof aiSessionLog>;
|
||||
export type ResourceAiProvider = InferSelectModel<typeof resourceAiProviders>;
|
||||
export type SiteResourceAiProvider = InferSelectModel<
|
||||
typeof siteResourceAiProviders
|
||||
>;
|
||||
export type ResourceAiModel = InferSelectModel<typeof resourceAiModels>;
|
||||
export type SiteResourceAiModel = InferSelectModel<typeof siteResourceAiModels>;
|
||||
export type Certificate = InferSelectModel<typeof certificates>;
|
||||
|
||||
@@ -33,7 +33,9 @@ import {
|
||||
resourcePolicyPassword,
|
||||
ResourcePolicyPassword,
|
||||
resourcePolicyHeaderAuth,
|
||||
ResourcePolicyHeaderAuth
|
||||
ResourcePolicyHeaderAuth,
|
||||
resourceWhitelist,
|
||||
resourcePolicyWhiteList
|
||||
} from "@server/db";
|
||||
import { alias } from "@server/db";
|
||||
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
||||
@@ -448,6 +450,36 @@ export async function getResourceRules(
|
||||
return [...directRules, ...offsetPolicyRules] as ResourceRule[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the whitelisted email associated with a resource session's whitelist
|
||||
* match (either a direct resource whitelist entry or a resource policy
|
||||
* whitelist entry).
|
||||
*/
|
||||
export async function getWhitelistEmail(
|
||||
whitelistId?: number | null,
|
||||
policyWhitelistId?: number | null
|
||||
): Promise<string | null> {
|
||||
if (whitelistId) {
|
||||
const [row] = await db
|
||||
.select({ email: resourceWhitelist.email })
|
||||
.from(resourceWhitelist)
|
||||
.where(eq(resourceWhitelist.whitelistId, whitelistId))
|
||||
.limit(1);
|
||||
return row?.email ?? null;
|
||||
}
|
||||
|
||||
if (policyWhitelistId) {
|
||||
const [row] = await db
|
||||
.select({ email: resourcePolicyWhiteList.email })
|
||||
.from(resourcePolicyWhiteList)
|
||||
.where(eq(resourcePolicyWhiteList.whitelistId, policyWhitelistId))
|
||||
.limit(1);
|
||||
return row?.email ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organization login page
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import { APP_PATH } from "@server/lib/consts";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import logger from "@server/logger";
|
||||
|
||||
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
export const exists = checkFileExists(location);
|
||||
@@ -12,7 +13,11 @@ export const exists = checkFileExists(location);
|
||||
bootstrapVolume();
|
||||
|
||||
function createDb() {
|
||||
const sqlite = new Database(location);
|
||||
const verbose =
|
||||
process.env.QUERY_LOGGING == "true"
|
||||
? (message: unknown) => logger.debug(String(message))
|
||||
: undefined;
|
||||
const sqlite = new Database(location, { verbose });
|
||||
|
||||
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
|
||||
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
||||
|
||||
@@ -23,25 +23,6 @@ import {
|
||||
users
|
||||
} from "./schema";
|
||||
|
||||
export const certificates = sqliteTable("certificates", {
|
||||
certId: integer("certId").primaryKey({ autoIncrement: true }),
|
||||
domain: text("domain").notNull().unique(),
|
||||
domainId: text("domainId").references(() => domains.domainId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
wildcard: integer("wildcard", { mode: "boolean" }).default(false),
|
||||
status: text("status").notNull().default("pending"), // pending, requested, valid, expired, failed
|
||||
expiresAt: integer("expiresAt"),
|
||||
lastRenewalAttempt: integer("lastRenewalAttempt"),
|
||||
createdAt: integer("createdAt").notNull(),
|
||||
updatedAt: integer("updatedAt").notNull(),
|
||||
orderId: text("orderId"),
|
||||
errorMessage: text("errorMessage"),
|
||||
renewalCount: integer("renewalCount").default(0),
|
||||
certFile: text("certFile"),
|
||||
keyFile: text("keyFile")
|
||||
});
|
||||
|
||||
export const dnsChallenge = sqliteTable("dnsChallenges", {
|
||||
dnsChallengeId: integer("dnsChallengeId").primaryKey({
|
||||
autoIncrement: true
|
||||
@@ -89,7 +70,8 @@ export const subscriptions = sqliteTable("subscriptions", {
|
||||
expiresAt: integer("expiresAt"),
|
||||
trial: integer("trial", { mode: "boolean" }).default(false),
|
||||
billingCycleAnchor: integer("billingCycleAnchor"),
|
||||
type: text("type") // tier1, tier2, tier3, or license
|
||||
type: text("type"), // tier1, tier2, tier3, or license
|
||||
override: integer("override", { mode: "boolean" }).default(false)
|
||||
});
|
||||
|
||||
export const subscriptionItems = sqliteTable("subscriptionItems", {
|
||||
@@ -477,6 +459,9 @@ export const eventStreamingDestinations = sqliteTable(
|
||||
sendAccessLogs: integer("sendAccessLogs", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
sendAISessionLogs: integer("sendAISessionLogs", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
type: text("type").notNull(), // e.g. "http", "kafka", etc.
|
||||
config: text("config").notNull(), // JSON string with the configuration for the destination
|
||||
enabled: integer("enabled", { mode: "boolean" })
|
||||
@@ -627,7 +612,6 @@ export const trialNotifications = sqliteTable("trialNotifications", {
|
||||
export type Approval = InferSelectModel<typeof approvals>;
|
||||
export type Limit = InferSelectModel<typeof limits>;
|
||||
export type Account = InferSelectModel<typeof account>;
|
||||
export type Certificate = InferSelectModel<typeof certificates>;
|
||||
export type DnsChallenge = InferSelectModel<typeof dnsChallenge>;
|
||||
export type Customer = InferSelectModel<typeof customers>;
|
||||
export type Subscription = InferSelectModel<typeof subscriptions>;
|
||||
|
||||
+886
-267
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user