From 82f990eb8b48ed60ac6809cb797bc7d842e91b44 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sun, 16 Feb 2025 18:09:17 -0500 Subject: [PATCH] add list domains for org endpoint --- server/auth/actions.ts | 1 + server/db/schema.ts | 1 + server/routers/domain/index.ts | 1 + server/routers/domain/listDomains.ts | 108 +++++++++++++++++++++ server/routers/external.ts | 8 ++ server/routers/org/createOrg.ts | 5 +- server/routers/traefik/getTraefikConfig.ts | 19 ++-- 7 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 server/routers/domain/index.ts create mode 100644 server/routers/domain/listDomains.ts diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 001b9a6c..4510c55b 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -62,6 +62,7 @@ export enum ActionsEnum { deleteResourceRule = "deleteResourceRule", listResourceRules = "listResourceRules", updateResourceRule = "updateResourceRule", + listOrgDomains = "listOrgDomains", } export async function checkUserActionPermission( diff --git a/server/db/schema.ts b/server/db/schema.ts index 6cbe0fc5..33d979e7 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -438,3 +438,4 @@ export type ResourceAccessToken = InferSelectModel; export type ResourceWhitelist = InferSelectModel; export type VersionMigration = InferSelectModel; export type ResourceRule = InferSelectModel; +export type Domain = InferSelectModel; diff --git a/server/routers/domain/index.ts b/server/routers/domain/index.ts new file mode 100644 index 00000000..2233b069 --- /dev/null +++ b/server/routers/domain/index.ts @@ -0,0 +1 @@ +export * from "./listDomains"; diff --git a/server/routers/domain/listDomains.ts b/server/routers/domain/listDomains.ts new file mode 100644 index 00000000..a5140a31 --- /dev/null +++ b/server/routers/domain/listDomains.ts @@ -0,0 +1,108 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { domains, orgDomains, users } from "@server/db/schema"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { eq, sql } from "drizzle-orm"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; + +const listDomainsParamsSchema = z + .object({ + orgId: z.string() + }) + .strict(); + +const listDomainsSchema = z + .object({ + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.number().int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.number().int().nonnegative()) + }) + .strict(); + +async function queryDomains(orgId: string, limit: number, offset: number) { + return await db + .select({ + domainId: domains.domainId, + baseDomain: domains.baseDomain + }) + .from(orgDomains) + .where(eq(orgDomains.orgId, orgId)) + .leftJoin(domains, eq(domains.domainId, orgDomains.domainId)) + .limit(limit) + .offset(offset); +} + +export type ListDomainsResponse = { + domains: NonNullable>>; + pagination: { total: number; limit: number; offset: number }; +}; + +export async function listDomains( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = listDomainsSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedQuery.error).toString() + ) + ); + } + const { limit, offset } = parsedQuery.data; + + const parsedParams = listDomainsParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { orgId } = parsedParams.data; + + const domains = await queryDomains(orgId.toString(), limit, offset); + + const [{ count }] = await db + .select({ count: sql`count(*)` }) + .from(users); + + return response(res, { + data: { + domains, + pagination: { + total: count, + limit, + offset + } + }, + success: true, + error: false, + message: "Users retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/external.ts b/server/routers/external.ts index 19c57008..f22fb281 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -3,6 +3,7 @@ import config from "@server/lib/config"; import * as site from "./site"; import * as org from "./org"; import * as resource from "./resource"; +import * as domain from "./domain"; import * as target from "./target"; import * as user from "./user"; import * as auth from "./auth"; @@ -133,6 +134,13 @@ authenticated.get( resource.listResources ); +authenticated.get( + "/org/:orgId/domains", + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.listOrgDomains), + domain.listDomains +); + authenticated.post( "/org/:orgId/create-invite", verifyOrgAccess, diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index a6072a30..381ce20e 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -89,7 +89,10 @@ export async function createOrg( let org: Org | null = null; await db.transaction(async (trx) => { - const allDomains = await trx.select().from(domains); + const allDomains = await trx + .select() + .from(domains) + .where(eq(domains.configManaged, true)); const newOrg = await trx .insert(orgs) diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 29a4d2c0..55e0e290 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -26,6 +26,7 @@ export async function traefikConfigProvider( proxyPort: resources.proxyPort, protocol: resources.protocol, isBaseDomain: resources.isBaseDomain, + domainId: resources.domainId, // Site fields site: { siteId: sites.siteId, @@ -34,8 +35,7 @@ export async function traefikConfigProvider( }, // Org fields org: { - orgId: orgs.orgId, - domain: orgs.domain + orgId: orgs.orgId }, // Targets as a subquery targets: sql`json_group_array(json_object( @@ -105,15 +105,22 @@ export async function traefikConfigProvider( const site = resource.site; const org = resource.org; - if (!org.domain) { - continue; - } - const routerName = `${resource.resourceId}-router`; const serviceName = `${resource.resourceId}-service`; const fullDomain = `${resource.fullDomain}`; if (resource.http) { + if (!resource.domainId) { + continue; + } + + if (!resource.fullDomain) { + logger.error( + `Resource ${resource.resourceId} has no fullDomain` + ); + continue; + } + // HTTP configuration remains the same if (!resource.subdomain && !resource.isBaseDomain) { continue;