Merge branch 'dev' into refactor/show-if-client-needs-update

This commit is contained in:
Fred KISSIE
2026-06-11 21:05:31 +02:00
199 changed files with 3020 additions and 2228 deletions

View File

@@ -84,7 +84,7 @@ registry.registerPath({
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),

View File

@@ -33,7 +33,7 @@ registry.registerPath({
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),

View File

@@ -67,7 +67,7 @@ registry.registerPath({
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
@@ -95,7 +95,7 @@ registry.registerPath({
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),

View File

@@ -1,20 +1,21 @@
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import {
db,
exitNodes,
labels,
newts,
orgs,
remoteExitNodes,
roleSites,
siteLabels,
siteNetworks,
siteResources,
targets,
sites,
targets,
userSites,
labels,
siteLabels,
type Label
} from "@server/db";
import cache from "#dynamic/lib/cache";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import response from "@server/lib/response";
import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
@@ -23,11 +24,8 @@ import type { PaginatedResponse } from "@server/types/Pagination";
import { and, asc, desc, eq, inArray, like, or, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import semver from "semver";
import { z } from "zod";
import { fromError } from "zod-validation-error";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
const listSitesParamsSchema = z.strictObject({
orgId: z.string()
@@ -189,7 +187,7 @@ registry.registerPath({
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
@@ -236,27 +234,6 @@ export async function listSites(
);
}
let accessibleSites;
if (req.user) {
accessibleSites = await db
.select({
siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})`
})
.from(userSites)
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
.where(
or(
eq(userSites.userId, req.user!.userId),
inArray(roleSites.roleId, req.userOrgRoleIds!)
)
);
} else {
accessibleSites = await db
.select({ siteId: sites.siteId })
.from(sites)
.where(eq(sites.orgId, orgId));
}
const isLabelFeatureEnabled = await isLicensedOrSubscribed(
orgId,
tierMatrix.labels
@@ -273,14 +250,38 @@ export async function listSites(
labels: labelFilter
} = parsedQuery.data;
const accessibleSiteIds = accessibleSites.map((site) => site.siteId);
const conditions = [eq(sites.orgId, orgId)];
const conditions = [
and(
inArray(sites.siteId, accessibleSiteIds),
eq(sites.orgId, orgId)
)
];
if (req.user) {
const userAccessConditions = [
inArray(
sites.siteId,
db
.select({ siteId: userSites.siteId })
.from(userSites)
.where(eq(userSites.userId, req.user.userId))
)
];
const roleIds = req.userOrgRoleIds ?? [];
if (roleIds.length > 0) {
userAccessConditions.push(
inArray(
sites.siteId,
db
.select({ siteId: roleSites.siteId })
.from(roleSites)
.where(inArray(roleSites.roleId, roleIds))
)
);
}
conditions.push(
userAccessConditions.length === 1
? userAccessConditions[0]
: or(...userAccessConditions)!
);
}
if (typeof online !== "undefined") {
conditions.push(eq(sites.online, online));
@@ -327,17 +328,15 @@ export async function listSites(
)
);
}
conditions.push(or(...queryList));
conditions.push(or(...queryList)!);
}
const baseQuery = querySitesBase().where(and(...conditions));
// we need to add `as` so that drizzle filters the result as a subquery
const countQuery = db.$count(
querySitesBase()
.where(and(...conditions))
.as("filtered_sites")
);
const countQuery = db
.select({ count: sql<number>`count(*)` })
.from(sites)
.where(and(...conditions));
const siteListQuery = baseQuery
.limit(pageSize)
@@ -350,11 +349,13 @@ export async function listSites(
: asc(sites.name)
);
const [totalCount, rows] = await Promise.all([
const [countRows, rows] = await Promise.all([
countQuery,
siteListQuery
]);
const totalCount = Number(countRows[0]?.count ?? 0);
const siteIds = rows.map((site) => site.siteId);
let labelsForSites: Array<{

View File

@@ -50,7 +50,7 @@ registry.registerPath({
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),