mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-02 10:38:01 +00:00
Handle deleting client and orphaning resources
This commit is contained in:
@@ -47,6 +47,7 @@ const createSiteResourceSchema = z
|
||||
ssl: z.boolean().optional(), // only used for http mode
|
||||
scheme: z.enum(["http", "https"]).optional(),
|
||||
siteIds: z.array(z.int()),
|
||||
siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided
|
||||
// proxyPort: z.int().positive().optional(),
|
||||
destinationPort: z.int().positive().optional(),
|
||||
destination: z.string().min(1),
|
||||
@@ -187,7 +188,8 @@ export async function createSiteResource(
|
||||
const {
|
||||
name,
|
||||
niceId,
|
||||
siteIds,
|
||||
siteIds: siteIdsInput,
|
||||
siteId,
|
||||
mode,
|
||||
scheme,
|
||||
// proxyPort,
|
||||
@@ -208,6 +210,12 @@ export async function createSiteResource(
|
||||
subdomain
|
||||
} = parsedBody.data;
|
||||
|
||||
// Backward compatibility: merge deprecated siteId into siteIds array
|
||||
const siteIds = [...siteIdsInput];
|
||||
if (siteId !== undefined && !siteIds.includes(siteId)) {
|
||||
siteIds.push(siteId);
|
||||
}
|
||||
|
||||
if (mode == "http") {
|
||||
const hasHttpFeature = await isLicensedOrSubscribed(
|
||||
orgId,
|
||||
|
||||
@@ -98,9 +98,11 @@ export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{
|
||||
*/
|
||||
function aggCol<T>(column: any) {
|
||||
if (DB_TYPE === "sqlite") {
|
||||
// json_group_array will include NULLs for left-joined missing rows;
|
||||
// we filter them out in transformSiteResourceRow keeping arrays aligned.
|
||||
return sql<T>`json_group_array(${column})`;
|
||||
}
|
||||
return sql<T>`array_agg(${column})`;
|
||||
return sql<T>`COALESCE(array_agg(${column}) FILTER (WHERE ${sites.siteId} IS NOT NULL), '{}')`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,16 +114,36 @@ function transformSiteResourceRow(row: any) {
|
||||
if (DB_TYPE !== "sqlite") {
|
||||
return row;
|
||||
}
|
||||
const siteIdsRaw = JSON.parse(row.siteIds) as (number | null)[];
|
||||
const siteNamesRaw = JSON.parse(row.siteNames) as (string | null)[];
|
||||
const siteNiceIdsRaw = JSON.parse(row.siteNiceIds) as (string | null)[];
|
||||
const siteAddressesRaw = JSON.parse(row.siteAddresses) as (string | null)[];
|
||||
const siteOnlinesRaw = JSON.parse(row.siteOnlines) as (0 | 1 | null)[];
|
||||
|
||||
// When a site resource has no associated sites (left join produced no
|
||||
// matches), the aggregated arrays will contain a single NULL entry. Strip
|
||||
// those out, keeping the parallel arrays aligned by siteId presence.
|
||||
const siteIds: number[] = [];
|
||||
const siteNames: string[] = [];
|
||||
const siteNiceIds: string[] = [];
|
||||
const siteAddresses: (string | null)[] = [];
|
||||
const siteOnlines: boolean[] = [];
|
||||
for (let i = 0; i < siteIdsRaw.length; i++) {
|
||||
if (siteIdsRaw[i] == null) continue;
|
||||
siteIds.push(siteIdsRaw[i] as number);
|
||||
siteNames.push((siteNamesRaw[i] ?? "") as string);
|
||||
siteNiceIds.push((siteNiceIdsRaw[i] ?? "") as string);
|
||||
siteAddresses.push(siteAddressesRaw[i] ?? null);
|
||||
siteOnlines.push(siteOnlinesRaw[i] === 1);
|
||||
}
|
||||
|
||||
return {
|
||||
...row,
|
||||
siteNames: JSON.parse(row.siteNames) as string[],
|
||||
siteNiceIds: JSON.parse(row.siteNiceIds) as string[],
|
||||
siteIds: JSON.parse(row.siteIds) as number[],
|
||||
siteAddresses: JSON.parse(row.siteAddresses) as (string | null)[],
|
||||
// SQLite stores booleans as 0/1 integers
|
||||
siteOnlines: (JSON.parse(row.siteOnlines) as (0 | 1)[]).map(
|
||||
(v) => v === 1
|
||||
) as boolean[]
|
||||
siteNames,
|
||||
siteNiceIds,
|
||||
siteIds,
|
||||
siteAddresses,
|
||||
siteOnlines
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,11 +180,11 @@ function querySiteResourcesBase() {
|
||||
siteOnlines: aggCol<boolean[]>(sites.online)
|
||||
})
|
||||
.from(siteResources)
|
||||
.innerJoin(
|
||||
.leftJoin(
|
||||
siteNetworks,
|
||||
eq(siteResources.networkId, siteNetworks.networkId)
|
||||
)
|
||||
.innerJoin(sites, eq(siteNetworks.siteId, sites.siteId))
|
||||
.leftJoin(sites, eq(siteNetworks.siteId, sites.siteId))
|
||||
.groupBy(siteResources.siteResourceId);
|
||||
}
|
||||
|
||||
@@ -215,6 +237,8 @@ export async function listAllSiteResourcesByOrg(
|
||||
const conditions = [and(eq(siteResources.orgId, orgId))];
|
||||
|
||||
if (siteId != null) {
|
||||
// Keep inner joins here: filtering by a specific site implies the
|
||||
// resource must have at least one matching site.
|
||||
const resourcesForSite = db
|
||||
.select({ id: siteResources.siteResourceId })
|
||||
.from(siteResources)
|
||||
|
||||
@@ -44,6 +44,7 @@ const updateSiteResourceSchema = z
|
||||
.strictObject({
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
siteIds: z.array(z.int()),
|
||||
siteId: z.int().positive().optional(),
|
||||
// niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(),
|
||||
niceId: z
|
||||
.string()
|
||||
@@ -196,7 +197,8 @@ export async function updateSiteResource(
|
||||
const { siteResourceId } = parsedParams.data;
|
||||
const {
|
||||
name,
|
||||
siteIds, // because it can change
|
||||
siteIds: siteIdsInput, // because it can change
|
||||
siteId,
|
||||
niceId,
|
||||
mode,
|
||||
scheme,
|
||||
@@ -217,6 +219,12 @@ export async function updateSiteResource(
|
||||
subdomain
|
||||
} = parsedBody.data;
|
||||
|
||||
// Backward compatibility: merge deprecated siteId into siteIds array
|
||||
const siteIds = [...siteIdsInput];
|
||||
if (siteId !== undefined && !siteIds.includes(siteId)) {
|
||||
siteIds.push(siteId);
|
||||
}
|
||||
|
||||
// Check if site resource exists
|
||||
const [existingSiteResource] = await db
|
||||
.select()
|
||||
|
||||
Reference in New Issue
Block a user