handle private resources filtering by labels

This commit is contained in:
Fred KISSIE
2026-05-12 20:24:34 +02:00
parent ec794d5de2
commit 9378103ddd

View File

@@ -1,4 +1,14 @@
import { db, DB_TYPE, SiteResource, siteNetworks, siteResources, sites } from "@server/db"; import {
db,
DB_TYPE,
Label,
SiteResource,
siteNetworks,
siteResourceLabels,
siteResources,
sites,
labels
} from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import logger from "@server/logger"; import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
@@ -9,6 +19,8 @@ import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { z } from "zod"; import { z } from "zod";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
const listAllSiteResourcesByOrgParamsSchema = z.strictObject({ const listAllSiteResourcesByOrgParamsSchema = z.strictObject({
orgId: z.string() orgId: z.string()
@@ -69,12 +81,7 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({
default: "asc", default: "asc",
description: "Sort order" description: "Sort order"
}), }),
siteId: z.coerce siteId: z.coerce.number<string>().int().positive().optional().openapi({
.number<string>()
.int()
.positive()
.optional()
.openapi({
type: "integer", type: "integer",
description: description:
"When set, only site resources associated with this site (via network) are returned" "When set, only site resources associated with this site (via network) are returned"
@@ -88,6 +95,7 @@ export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{
siteNames: string[]; siteNames: string[];
siteNiceIds: string[]; siteNiceIds: string[];
siteAddresses: (string | null)[]; siteAddresses: (string | null)[];
labels?: Array<Pick<Label, "labelId" | "name" | "color">>;
})[]; })[];
}>; }>;
@@ -234,6 +242,11 @@ export async function listAllSiteResourcesByOrg(
const { page, pageSize, query, mode, sort_by, order, siteId } = const { page, pageSize, query, mode, sort_by, order, siteId } =
parsedQuery.data; parsedQuery.data;
const isLabelFeatureEnabled = await isLicensedOrSubscribed(
orgId,
tierMatrix.labels
);
const conditions = [and(eq(siteResources.orgId, orgId))]; const conditions = [and(eq(siteResources.orgId, orgId))];
if (siteId != null) { if (siteId != null) {
@@ -258,39 +271,39 @@ export async function listAllSiteResourcesByOrg(
inArray(siteResources.siteResourceId, resourcesForSite) inArray(siteResources.siteResourceId, resourcesForSite)
); );
} }
if (mode) {
conditions.push(eq(siteResources.mode, mode));
}
if (query) { if (query) {
conditions.push( const q = "%" + query.toLowerCase() + "%";
or( const queryList = [
like( like(sql`LOWER(${siteResources.name})`, q),
sql`LOWER(${siteResources.name})`, like(sql`LOWER(${siteResources.niceId})`, q),
"%" + query.toLowerCase() + "%" like(sql`LOWER(${siteResources.destination})`, q),
), like(sql`LOWER(${siteResources.alias})`, q),
like( like(sql`LOWER(${siteResources.aliasAddress})`, q),
sql`LOWER(${siteResources.niceId})`, like(sql`LOWER(${sites.name})`, q)
"%" + query.toLowerCase() + "%" ];
),
like( if (isLabelFeatureEnabled) {
sql`LOWER(${siteResources.destination})`, queryList.push(
"%" + query.toLowerCase() + "%" inArray(
), siteResources.siteResourceId,
like( db
sql`LOWER(${siteResources.alias})`, .select({ id: siteResourceLabels.siteResourceId })
"%" + query.toLowerCase() + "%" .from(siteResourceLabels)
), .innerJoin(
like( labels,
sql`LOWER(${siteResources.aliasAddress})`, eq(labels.labelId, siteResourceLabels.labelId)
"%" + query.toLowerCase() + "%"
),
like(
sql`LOWER(${sites.name})`,
"%" + query.toLowerCase() + "%"
) )
.where(like(sql`LOWER(${labels.name})`, q))
) )
); );
} }
if (mode) { conditions.push(or(...queryList));
conditions.push(eq(siteResources.mode, mode));
} }
const baseQuery = querySiteResourcesBase().where(and(...conditions)); const baseQuery = querySiteResourcesBase().where(and(...conditions));
@@ -315,11 +328,51 @@ export async function listAllSiteResourcesByOrg(
countQuery countQuery
]); ]);
const siteResourcesList = siteResourcesRaw.map(transformSiteResourceRow); const siteResourcesList = siteResourcesRaw.map(
transformSiteResourceRow
);
const siteResourceIdList = siteResourcesList.map(
(r) => r.siteResourceId
);
let labelsForSiteResources: Array<{
labelId: number;
name: string;
color: string;
siteResourceId: number;
}> = [];
if (isLabelFeatureEnabled && siteResourceIdList.length > 0) {
labelsForSiteResources = await db
.select({
labelId: labels.labelId,
name: labels.name,
color: labels.color,
siteResourceId: siteResourceLabels.siteResourceId
})
.from(labels)
.innerJoin(
siteResourceLabels,
eq(siteResourceLabels.labelId, labels.labelId)
)
.where(
inArray(
siteResourceLabels.siteResourceId,
siteResourceIdList
)
)
.orderBy(asc(siteResourceLabels.siteResourceLabelId));
}
return response<ListAllSiteResourcesByOrgResponse>(res, { return response<ListAllSiteResourcesByOrgResponse>(res, {
data: { data: {
siteResources: siteResourcesList, siteResources: siteResourcesList.map((r) => ({
...r,
labels: labelsForSiteResources.filter(
(l) => l.siteResourceId === r.siteResourceId
)
})),
pagination: { pagination: {
total: totalCount, total: totalCount,
pageSize, pageSize,