mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-19 20:16:32 +02:00
support labels in list alises
This commit is contained in:
@@ -5,8 +5,12 @@ import {
|
|||||||
userSiteResources,
|
userSiteResources,
|
||||||
roleSiteResources,
|
roleSiteResources,
|
||||||
userOrgRoles,
|
userOrgRoles,
|
||||||
userOrgs
|
userOrgs,
|
||||||
|
labels,
|
||||||
|
siteResourceLabels
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
|
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||||
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { and, eq, inArray, asc, isNotNull, ne, or } from "drizzle-orm";
|
import { and, eq, inArray, asc, isNotNull, ne, or } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -19,13 +23,33 @@ import { regionalCache as cache } from "#dynamic/lib/cache";
|
|||||||
|
|
||||||
const USER_RESOURCE_ALIASES_CACHE_TTL_SEC = 60;
|
const USER_RESOURCE_ALIASES_CACHE_TTL_SEC = 60;
|
||||||
|
|
||||||
|
const labelFilterQuerySchema = z
|
||||||
|
.preprocess((val) => {
|
||||||
|
if (val === undefined || val === null || val === "") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (typeof val === "string") {
|
||||||
|
return val.split(",");
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, z.array(z.string()))
|
||||||
|
.optional()
|
||||||
|
.catch([]);
|
||||||
|
|
||||||
function userResourceAliasesCacheKey(
|
function userResourceAliasesCacheKey(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
page: number,
|
page: number,
|
||||||
pageSize: number
|
pageSize: number,
|
||||||
|
includeLabels: boolean,
|
||||||
|
labelFilter: string[]
|
||||||
) {
|
) {
|
||||||
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}`;
|
const labelsKey =
|
||||||
|
labelFilter.length > 0 ? labelFilter.slice().sort().join(",") : "all";
|
||||||
|
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listUserResourceAliasesParamsSchema = z.strictObject({
|
const listUserResourceAliasesParamsSchema = z.strictObject({
|
||||||
@@ -56,43 +80,35 @@ const listUserResourceAliasesQuerySchema = z.strictObject({
|
|||||||
type: "integer",
|
type: "integer",
|
||||||
default: 1,
|
default: 1,
|
||||||
description: "Page number to retrieve"
|
description: "Page number to retrieve"
|
||||||
})
|
}),
|
||||||
|
includeLabels: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.optional()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true")
|
||||||
|
.openapi({
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
"When true, include label names for each alias in the items field"
|
||||||
|
}),
|
||||||
|
labels: labelFilterQuerySchema.openapi({
|
||||||
|
type: "array",
|
||||||
|
description:
|
||||||
|
"Filter by resource labels. A resource matches when it has any of the given labels (OR)."
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type UserResourceAliasItem = {
|
||||||
|
alias: string;
|
||||||
|
labels: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ListUserResourceAliasesResponse = PaginatedResponse<{
|
export type ListUserResourceAliasesResponse = PaginatedResponse<{
|
||||||
aliases: string[];
|
aliases: string[];
|
||||||
|
items?: UserResourceAliasItem[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// registry.registerPath({
|
|
||||||
// method: "get",
|
|
||||||
// path: "/org/{orgId}/user-resource-aliases",
|
|
||||||
// description:
|
|
||||||
// "List private (host-mode) site resource aliases the authenticated user can access in the organization, paginated.",
|
|
||||||
// tags: [OpenAPITags.PrivateResource],
|
|
||||||
// request: {
|
|
||||||
// params: z.object({
|
|
||||||
// orgId: z.string()
|
|
||||||
// }),
|
|
||||||
// query: listUserResourceAliasesQuerySchema
|
|
||||||
// },
|
|
||||||
// responses: {
|
|
||||||
// 200: {
|
|
||||||
// description: "Successful response",
|
|
||||||
// content: {
|
|
||||||
// "application/json": {
|
|
||||||
// schema: z.object({
|
|
||||||
// data: z.record(z.string(), z.any()).nullable(),
|
|
||||||
// success: z.boolean(),
|
|
||||||
// error: z.boolean(),
|
|
||||||
// message: z.string(),
|
|
||||||
// status: z.number()
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
export async function listUserResourceAliases(
|
export async function listUserResourceAliases(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
@@ -110,7 +126,12 @@ export async function listUserResourceAliases(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { page, pageSize } = parsedQuery.data;
|
const {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
includeLabels,
|
||||||
|
labels: labelFilter
|
||||||
|
} = parsedQuery.data;
|
||||||
|
|
||||||
const parsedParams = listUserResourceAliasesParamsSchema.safeParse(
|
const parsedParams = listUserResourceAliasesParamsSchema.safeParse(
|
||||||
req.params
|
req.params
|
||||||
@@ -149,7 +170,9 @@ export async function listUserResourceAliases(
|
|||||||
orgId,
|
orgId,
|
||||||
userId,
|
userId,
|
||||||
page,
|
page,
|
||||||
pageSize
|
pageSize,
|
||||||
|
includeLabels,
|
||||||
|
labelFilter ?? []
|
||||||
);
|
);
|
||||||
const cachedData: ListUserResourceAliasesResponse | undefined =
|
const cachedData: ListUserResourceAliasesResponse | undefined =
|
||||||
await cache.get(cacheKey);
|
await cache.get(cacheKey);
|
||||||
@@ -204,6 +227,7 @@ export async function listUserResourceAliases(
|
|||||||
if (accessibleSiteResourceIds.length === 0) {
|
if (accessibleSiteResourceIds.length === 0) {
|
||||||
const data: ListUserResourceAliasesResponse = {
|
const data: ListUserResourceAliasesResponse = {
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
...(includeLabels ? { items: [] } : {}),
|
||||||
pagination: {
|
pagination: {
|
||||||
total: 0,
|
total: 0,
|
||||||
pageSize,
|
pageSize,
|
||||||
@@ -224,18 +248,44 @@ export async function listUserResourceAliases(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = and(
|
const isLabelFeatureEnabled = await isLicensedOrSubscribed(
|
||||||
|
orgId,
|
||||||
|
tierMatrix.labels
|
||||||
|
);
|
||||||
|
|
||||||
|
const whereConditions = [
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true),
|
||||||
or(eq(siteResources.mode, "host"), eq(siteResources.mode, "ssh")),
|
or(eq(siteResources.mode, "host"), eq(siteResources.mode, "ssh")),
|
||||||
isNotNull(siteResources.alias),
|
isNotNull(siteResources.alias),
|
||||||
ne(siteResources.alias, ""),
|
ne(siteResources.alias, ""),
|
||||||
inArray(siteResources.siteResourceId, accessibleSiteResourceIds)
|
inArray(siteResources.siteResourceId, accessibleSiteResourceIds)
|
||||||
);
|
];
|
||||||
|
|
||||||
|
if (isLabelFeatureEnabled && labelFilter && labelFilter.length > 0) {
|
||||||
|
whereConditions.push(
|
||||||
|
inArray(
|
||||||
|
siteResources.siteResourceId,
|
||||||
|
db
|
||||||
|
.select({ id: siteResourceLabels.siteResourceId })
|
||||||
|
.from(siteResourceLabels)
|
||||||
|
.innerJoin(
|
||||||
|
labels,
|
||||||
|
eq(labels.labelId, siteResourceLabels.labelId)
|
||||||
|
)
|
||||||
|
.where(inArray(labels.name, labelFilter))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereClause = and(...whereConditions);
|
||||||
|
|
||||||
const baseSelect = () =>
|
const baseSelect = () =>
|
||||||
db
|
db
|
||||||
.select({ alias: siteResources.alias })
|
.select({
|
||||||
|
alias: siteResources.alias,
|
||||||
|
siteResourceId: siteResources.siteResourceId
|
||||||
|
})
|
||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(whereClause);
|
.where(whereClause);
|
||||||
|
|
||||||
@@ -251,8 +301,46 @@ export async function listUserResourceAliases(
|
|||||||
|
|
||||||
const aliases = rows.map((r) => r.alias as string);
|
const aliases = rows.map((r) => r.alias as string);
|
||||||
|
|
||||||
|
let items: UserResourceAliasItem[] | undefined;
|
||||||
|
if (includeLabels) {
|
||||||
|
const siteResourceIdList = rows.map((r) => r.siteResourceId);
|
||||||
|
|
||||||
|
let labelsForSiteResources: Array<{
|
||||||
|
name: string;
|
||||||
|
siteResourceId: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
if (isLabelFeatureEnabled && siteResourceIdList.length > 0) {
|
||||||
|
labelsForSiteResources = await db
|
||||||
|
.select({
|
||||||
|
name: labels.name,
|
||||||
|
siteResourceId: siteResourceLabels.siteResourceId
|
||||||
|
})
|
||||||
|
.from(labels)
|
||||||
|
.innerJoin(
|
||||||
|
siteResourceLabels,
|
||||||
|
eq(siteResourceLabels.labelId, labels.labelId)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
inArray(
|
||||||
|
siteResourceLabels.siteResourceId,
|
||||||
|
siteResourceIdList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(asc(siteResourceLabels.siteResourceLabelId));
|
||||||
|
}
|
||||||
|
|
||||||
|
items = rows.map((row) => ({
|
||||||
|
alias: row.alias as string,
|
||||||
|
labels: labelsForSiteResources
|
||||||
|
.filter((l) => l.siteResourceId === row.siteResourceId)
|
||||||
|
.map((l) => l.name)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const data: ListUserResourceAliasesResponse = {
|
const data: ListUserResourceAliasesResponse = {
|
||||||
aliases,
|
aliases,
|
||||||
|
...(items !== undefined ? { items } : {}),
|
||||||
pagination: {
|
pagination: {
|
||||||
total: totalCount,
|
total: totalCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
|||||||
Reference in New Issue
Block a user