Wrap in transactions

This commit is contained in:
Owen
2026-04-27 13:46:01 -07:00
parent 512ba2150b
commit 61aaa5a832
7 changed files with 337 additions and 272 deletions

View File

@@ -19,7 +19,11 @@ import { eq } from "drizzle-orm";
import { pickPort } from "./helpers";
import { isTargetValid } from "@server/lib/validators";
import { OpenAPITags, registry } from "@server/openApi";
import { fireHealthCheckHealthyAlert, fireHealthCheckUnhealthyAlert, fireHealthCheckUnknownAlert } from "#dynamic/lib/alerts";
import {
fireHealthCheckHealthyAlert,
fireHealthCheckUnhealthyAlert,
fireHealthCheckUnknownAlert
} from "#dynamic/lib/alerts";
const createTargetParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
@@ -142,151 +146,155 @@ export async function createTarget(
);
}
const existingTargets = await db
.select()
.from(targets)
.where(eq(targets.resourceId, resourceId));
const existingTarget = existingTargets.find(
(target) =>
target.ip === targetData.ip &&
target.port === targetData.port &&
target.method === targetData.method &&
target.siteId === targetData.siteId
);
if (existingTarget) {
// log a warning
logger.warn(
`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`
);
}
let newTarget: Target[] = [];
let healthCheck: TargetHealthCheck[] = [];
let targetIps: string[] = [];
if (site.type == "local") {
newTarget = await db
.insert(targets)
.values({
resourceId,
...targetData,
priority: targetData.priority || 100
})
.returning();
} else {
// make sure the target is within the site subnet
if (
site.type == "wireguard" &&
!isIpInCidr(targetData.ip, site.subnet!)
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Target IP is not within the site subnet`
)
let healthCheck: TargetHealthCheck[] = [];
await db.transaction(async (trx) => {
const existingTargets = await trx
.select()
.from(targets)
.where(eq(targets.resourceId, resourceId));
const existingTarget = existingTargets.find(
(target) =>
target.ip === targetData.ip &&
target.port === targetData.port &&
target.method === targetData.method &&
target.siteId === targetData.siteId
);
if (existingTarget) {
// log a warning
logger.warn(
`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`
);
}
const { internalPort, targetIps: newTargetIps } = await pickPort(
site.siteId!,
db
);
if (site.type == "local") {
newTarget = await trx
.insert(targets)
.values({
resourceId,
...targetData,
priority: targetData.priority || 100
})
.returning();
} else {
// make sure the target is within the site subnet
if (
site.type == "wireguard" &&
!isIpInCidr(targetData.ip, site.subnet!)
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Target IP is not within the site subnet`
)
);
}
if (!internalPort) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`No available internal port`
)
);
const { internalPort, targetIps: newTargetIps } =
await pickPort(site.siteId!, trx);
if (!internalPort) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`No available internal port`
)
);
}
newTarget = await trx
.insert(targets)
.values({
resourceId,
siteId: site.siteId,
ip: targetData.ip,
method: targetData.method,
port: targetData.port,
internalPort,
enabled: targetData.enabled,
path: targetData.path,
pathMatchType: targetData.pathMatchType,
rewritePath: targetData.rewritePath,
rewritePathType: targetData.rewritePathType,
priority: targetData.priority || 100
})
.returning();
// add the new target to the targetIps array
newTargetIps.push(`${targetData.ip}/32`);
targetIps = newTargetIps;
}
newTarget = await db
.insert(targets)
let hcHeaders = null;
if (targetData.hcHeaders) {
hcHeaders = JSON.stringify(targetData.hcHeaders);
}
healthCheck = await trx
.insert(targetHealthCheck)
.values({
resourceId,
siteId: site.siteId,
ip: targetData.ip,
method: targetData.method,
port: targetData.port,
internalPort,
enabled: targetData.enabled,
path: targetData.path,
pathMatchType: targetData.pathMatchType,
rewritePath: targetData.rewritePath,
rewritePathType: targetData.rewritePathType,
priority: targetData.priority || 100
orgId: resource.orgId,
targetId: newTarget[0].targetId,
siteId: targetData.siteId,
name: `Resource ${resource.name} - ${targetData.ip}:${targetData.port}`,
hcEnabled: targetData.hcEnabled ?? false,
hcPath: targetData.hcPath ?? null,
hcScheme: targetData.hcScheme ?? null,
hcMode: targetData.hcMode ?? null,
hcHostname: targetData.hcHostname ?? null,
hcPort: targetData.hcPort ?? null,
hcInterval: targetData.hcInterval ?? null,
hcUnhealthyInterval: targetData.hcUnhealthyInterval ?? null,
hcTimeout: targetData.hcTimeout ?? null,
hcHeaders: hcHeaders,
hcFollowRedirects: targetData.hcFollowRedirects ?? null,
hcMethod: targetData.hcMethod ?? null,
hcStatus: targetData.hcStatus ?? null,
hcHealth: targetData.hcEnabled ? "unhealthy" : "unknown",
hcTlsServerName: targetData.hcTlsServerName ?? null,
hcHealthyThreshold: targetData.hcHealthyThreshold ?? null,
hcUnhealthyThreshold:
targetData.hcUnhealthyThreshold ?? null
})
.returning();
// add the new target to the targetIps array
newTargetIps.push(`${targetData.ip}/32`);
targetIps = newTargetIps;
}
let hcHeaders = null;
if (targetData.hcHeaders) {
hcHeaders = JSON.stringify(targetData.hcHeaders);
}
healthCheck = await db
.insert(targetHealthCheck)
.values({
orgId: resource.orgId,
targetId: newTarget[0].targetId,
siteId: targetData.siteId,
name: `Resource ${resource.name} - ${targetData.ip}:${targetData.port}`,
hcEnabled: targetData.hcEnabled ?? false,
hcPath: targetData.hcPath ?? null,
hcScheme: targetData.hcScheme ?? null,
hcMode: targetData.hcMode ?? null,
hcHostname: targetData.hcHostname ?? null,
hcPort: targetData.hcPort ?? null,
hcInterval: targetData.hcInterval ?? null,
hcUnhealthyInterval: targetData.hcUnhealthyInterval ?? null,
hcTimeout: targetData.hcTimeout ?? null,
hcHeaders: hcHeaders,
hcFollowRedirects: targetData.hcFollowRedirects ?? null,
hcMethod: targetData.hcMethod ?? null,
hcStatus: targetData.hcStatus ?? null,
hcHealth: targetData.hcEnabled ? "unhealthy" : "unknown",
hcTlsServerName: targetData.hcTlsServerName ?? null,
hcHealthyThreshold: targetData.hcHealthyThreshold ?? null,
hcUnhealthyThreshold: targetData.hcUnhealthyThreshold ?? null
})
.returning();
if (healthCheck[0].hcHealth === "unhealthy") {
await fireHealthCheckUnhealthyAlert(
healthCheck[0].orgId,
healthCheck[0].targetHealthCheckId,
healthCheck[0].name,
undefined,
undefined,
false // dont send the alert because we just want to create the alert, not notify users yet
);
} else if (healthCheck[0].hcHealth === "unknown") {
// if the health is unknown, we want to fire an alert to notify users to enable health checks
await fireHealthCheckUnknownAlert(
healthCheck[0].orgId,
healthCheck[0].targetHealthCheckId,
healthCheck[0].name,
undefined,
undefined,
false // dont send the alert because we just want to create the alert, not notify users yet
);
} else if (healthCheck[0].hcHealth === "healthy") {
await fireHealthCheckHealthyAlert(
healthCheck[0].orgId,
healthCheck[0].targetHealthCheckId,
healthCheck[0].name,
undefined,
undefined,
false // dont send the alert because we just want to create the alert, not notify users yet
);
}
if (healthCheck[0].hcHealth === "unhealthy") {
await fireHealthCheckUnhealthyAlert(
healthCheck[0].orgId,
healthCheck[0].targetHealthCheckId,
healthCheck[0].name,
undefined,
undefined,
false, // dont send the alert because we just want to create the alert, not notify users yet
trx
);
} else if (healthCheck[0].hcHealth === "unknown") {
// if the health is unknown, we want to fire an alert to notify users to enable health checks
await fireHealthCheckUnknownAlert(
healthCheck[0].orgId,
healthCheck[0].targetHealthCheckId,
healthCheck[0].name,
undefined,
undefined,
false, // dont send the alert because we just want to create the alert, not notify users yet
trx
);
} else if (healthCheck[0].hcHealth === "healthy") {
await fireHealthCheckHealthyAlert(
healthCheck[0].orgId,
healthCheck[0].targetHealthCheckId,
healthCheck[0].name,
undefined,
undefined,
false, // dont send the alert because we just want to create the alert, not notify users yet
trx
);
}
});
if (site.pubKey) {
if (site.type == "wireguard") {