From 4e721e0f54b2a5dad0bcb0adade51cc2234d6640 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 2 Oct 2024 21:17:38 -0400 Subject: [PATCH 1/2] Fix return response and add list --- server/routers/org/createOrg.ts | 2 +- server/routers/org/deleteOrg.ts | 2 +- server/routers/org/getOrg.ts | 2 +- server/routers/org/listOrgs.ts | 57 +++++++++++++++++ server/routers/org/updateOrg.ts | 2 +- server/routers/resource/createResource.ts | 2 +- server/routers/resource/deleteResource.ts | 2 +- server/routers/resource/getResource.ts | 2 +- server/routers/resource/listResources.ts | 70 +++++++++++++++++++++ server/routers/resource/updateResource.ts | 2 +- server/routers/site/deleteSite.ts | 2 +- server/routers/site/getSite.ts | 2 +- server/routers/site/listSites.ts | 76 +++++++++++++++++++++++ server/routers/site/updateSite.ts | 2 +- server/routers/target/createTarget.ts | 2 +- server/routers/target/deleteTarget.ts | 2 +- server/routers/target/getTarget.ts | 2 +- server/routers/target/listTargets.ts | 73 ++++++++++++++++++++++ server/routers/target/updateTarget.ts | 2 +- server/routers/user/deleteUser.ts | 2 +- server/routers/user/getUser.ts | 2 +- server/routers/user/listUsers.ts | 61 ++++++++++++++++++ 22 files changed, 354 insertions(+), 17 deletions(-) create mode 100644 server/routers/org/listOrgs.ts create mode 100644 server/routers/resource/listResources.ts create mode 100644 server/routers/site/listSites.ts create mode 100644 server/routers/target/listTargets.ts create mode 100644 server/routers/user/listUsers.ts diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index ee8ce276..048f2dc9 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -31,7 +31,7 @@ export async function createOrg(req: Request, res: Response, next: NextFunction) }).returning(); return res.status(HttpCode.CREATED).send( - response({ + response(res, { data: newOrg[0], success: true, error: false, diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index aea15399..e4aaa819 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -39,7 +39,7 @@ export async function deleteOrg(req: Request, res: Response, next: NextFunction) } return res.status(HttpCode.OK).send( - response({ + response(res, { data: null, success: true, error: false, diff --git a/server/routers/org/getOrg.ts b/server/routers/org/getOrg.ts index acd81ef7..95d3f7d2 100644 --- a/server/routers/org/getOrg.ts +++ b/server/routers/org/getOrg.ts @@ -40,7 +40,7 @@ export async function getOrg(req: Request, res: Response, next: NextFunction): P } return res.status(HttpCode.OK).send( - response({ + response(res, { data: org[0], success: true, error: false, diff --git a/server/routers/org/listOrgs.ts b/server/routers/org/listOrgs.ts new file mode 100644 index 00000000..8ece3110 --- /dev/null +++ b/server/routers/org/listOrgs.ts @@ -0,0 +1,57 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { orgs } from '@server/db/schema'; +import response from "@server/utils/response"; +import HttpCode from '@server/types/HttpCode'; +import createHttpError from 'http-errors'; +import { sql } from 'drizzle-orm'; + +const listOrgsSchema = z.object({ + limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), + offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), +}); + +export async function listOrgs(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedQuery = listOrgsSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { limit, offset } = parsedQuery.data; + + const organizations = await db.select() + .from(orgs) + .limit(limit) + .offset(offset); + + const totalCountResult = await db.select({ count: sql`cast(count(*) as integer)` }) + .from(orgs); + const totalCount = totalCountResult[0].count; + + return res.status(HttpCode.OK).send( + response(res, { + data: { + organizations, + pagination: { + total: totalCount, + limit, + offset, + }, + }, + success: true, + error: false, + message: "Organizations retrieved successfully", + status: HttpCode.OK, + }) + ); + } catch (error) { + next(error); + } +} \ No newline at end of file diff --git a/server/routers/org/updateOrg.ts b/server/routers/org/updateOrg.ts index 2311b412..cfd8bdac 100644 --- a/server/routers/org/updateOrg.ts +++ b/server/routers/org/updateOrg.ts @@ -58,7 +58,7 @@ export async function updateOrg(req: Request, res: Response, next: NextFunction) } return res.status(HttpCode.OK).send( - response({ + response(res, { data: updatedOrg[0], success: true, error: false, diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index 1c45f620..2dcd05b8 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -40,7 +40,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc }).returning(); return res.status(HttpCode.CREATED).send( - response({ + response(res, { data: newResource[0], success: true, error: false, diff --git a/server/routers/resource/deleteResource.ts b/server/routers/resource/deleteResource.ts index dbc11c00..43e28094 100644 --- a/server/routers/resource/deleteResource.ts +++ b/server/routers/resource/deleteResource.ts @@ -42,7 +42,7 @@ export async function deleteResource(req: Request, res: Response, next: NextFunc } return res.status(HttpCode.OK).send( - response({ + response(res, { data: null, success: true, error: false, diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index 58decd69..8343056c 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -43,7 +43,7 @@ export async function getResource(req: Request, res: Response, next: NextFunctio } return res.status(HttpCode.OK).send( - response({ + response(res, { data: resource[0], success: true, error: false, diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts new file mode 100644 index 00000000..adf560b9 --- /dev/null +++ b/server/routers/resource/listResources.ts @@ -0,0 +1,70 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { resources, sites } from '@server/db/schema'; +import response from "@server/utils/response"; +import HttpCode from '@server/types/HttpCode'; +import createHttpError from 'http-errors'; +import { sql, eq } from 'drizzle-orm'; + +const listResourcesSchema = z.object({ + limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), + offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), + siteId: z.string().optional().transform(Number).pipe(z.number().int().positive()), +}); + +export async function listResources(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedQuery = listResourcesSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { limit, offset, siteId } = parsedQuery.data; + + let baseQuery = db + .select({ + resourceId: resources.resourceId, + name: resources.name, + subdomain: resources.subdomain, + siteName: sites.name, + }) + .from(resources) + .leftJoin(sites, eq(resources.siteId, sites.siteId)); + + let countQuery = db.select({ count: sql`cast(count(*) as integer)` }).from(resources); + + if (siteId) { + baseQuery = baseQuery.where(eq(resources.siteId, siteId)); + countQuery = countQuery.where(eq(resources.siteId, siteId)); + } + + const resourcesList = await baseQuery.limit(limit).offset(offset); + const totalCountResult = await countQuery; + const totalCount = totalCountResult[0].count; + + return res.status(HttpCode.OK).send( + response(res, { + data: { + resources: resourcesList, + pagination: { + total: totalCount, + limit, + offset, + }, + }, + success: true, + error: false, + message: "Resources retrieved successfully", + status: HttpCode.OK, + }) + ); + } catch (error) { + next(error); + } +} \ No newline at end of file diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 09b6486f..96b14152 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -63,7 +63,7 @@ export async function updateResource(req: Request, res: Response, next: NextFunc } return res.status(HttpCode.OK).send( - response({ + response(res, { data: updatedResource[0], success: true, error: false, diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index a982169d..16d26eae 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -42,7 +42,7 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction } return res.status(HttpCode.OK).send( - response({ + response(res, { data: null, success: true, error: false, diff --git a/server/routers/site/getSite.ts b/server/routers/site/getSite.ts index 2049a888..5ceead1c 100644 --- a/server/routers/site/getSite.ts +++ b/server/routers/site/getSite.ts @@ -43,7 +43,7 @@ export async function getSite(req: Request, res: Response, next: NextFunction): } return res.status(HttpCode.OK).send( - response({ + response(res, { data: site[0], success: true, error: false, diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts new file mode 100644 index 00000000..f808804e --- /dev/null +++ b/server/routers/site/listSites.ts @@ -0,0 +1,76 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { sites, orgs, exitNodes } from '@server/db/schema'; +import response from "@server/utils/response"; +import HttpCode from '@server/types/HttpCode'; +import createHttpError from 'http-errors'; +import { sql, eq } from 'drizzle-orm'; + +const listSitesSchema = z.object({ + limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), + offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), + orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()), +}); + +export async function listSites(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedQuery = listSitesSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { limit, offset, orgId } = parsedQuery.data; + + let baseQuery: any = db + .select({ + siteId: sites.siteId, + name: sites.name, + subdomain: sites.subdomain, + pubKey: sites.pubKey, + subnet: sites.subnet, + megabytesIn: sites.megabytesIn, + megabytesOut: sites.megabytesOut, + orgName: orgs.name, + exitNodeName: exitNodes.name, + }) + .from(sites) + .leftJoin(orgs, eq(sites.orgId, orgs.orgId)) + .leftJoin(exitNodes, eq(sites.exitNode, exitNodes.exitNodeId)); + + let countQuery: any = db.select({ count: sql`cast(count(*) as integer)` }).from(sites); + + if (orgId) { + baseQuery = baseQuery.where(eq(sites.orgId, orgId)); + countQuery = countQuery.where(eq(sites.orgId, orgId)); + } + + const sitesList = await baseQuery.limit(limit).offset(offset); + const totalCountResult = await countQuery; + const totalCount = totalCountResult[0].count; + + return res.status(HttpCode.OK).send( + response(res, { + data: { + sites: sitesList, + pagination: { + total: totalCount, + limit, + offset, + }, + }, + success: true, + error: false, + message: "Sites retrieved successfully", + status: HttpCode.OK, + }) + ); + } catch (error) { + next(error); + } +} \ No newline at end of file diff --git a/server/routers/site/updateSite.ts b/server/routers/site/updateSite.ts index e32a9d8f..66b1e5aa 100644 --- a/server/routers/site/updateSite.ts +++ b/server/routers/site/updateSite.ts @@ -68,7 +68,7 @@ export async function updateSite(req: Request, res: Response, next: NextFunction } return res.status(HttpCode.OK).send( - response({ + response(res, { data: updatedSite[0], success: true, error: false, diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index c51caf93..71071764 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -32,7 +32,7 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti const newTarget = await db.insert(targets).values(targetData).returning(); return res.status(HttpCode.CREATED).send( - response({ + response(res, { data: newTarget[0], success: true, error: false, diff --git a/server/routers/target/deleteTarget.ts b/server/routers/target/deleteTarget.ts index 934a8c00..a684fcec 100644 --- a/server/routers/target/deleteTarget.ts +++ b/server/routers/target/deleteTarget.ts @@ -39,7 +39,7 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti } return res.status(HttpCode.OK).send( - response({ + response(res, { data: null, success: true, error: false, diff --git a/server/routers/target/getTarget.ts b/server/routers/target/getTarget.ts index 918dc8a7..96ce352e 100644 --- a/server/routers/target/getTarget.ts +++ b/server/routers/target/getTarget.ts @@ -40,7 +40,7 @@ export async function getTarget(req: Request, res: Response, next: NextFunction) } return res.status(HttpCode.OK).send( - response({ + response(res, { data: target[0], success: true, error: false, diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts new file mode 100644 index 00000000..f8a32c85 --- /dev/null +++ b/server/routers/target/listTargets.ts @@ -0,0 +1,73 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { targets, resources } from '@server/db/schema'; +import response from "@server/utils/response"; +import HttpCode from '@server/types/HttpCode'; +import createHttpError from 'http-errors'; +import { sql, eq } from 'drizzle-orm'; + +const listTargetsSchema = z.object({ + limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), + offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), + resourceId: z.string().optional(), +}); + +export async function listTargets(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedQuery = listTargetsSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { limit, offset, resourceId } = parsedQuery.data; + + let baseQuery: any = db + .select({ + targetId: targets.targetId, + ip: targets.ip, + method: targets.method, + port: targets.port, + protocol: targets.protocol, + enabled: targets.enabled, + resourceName: resources.name, + }) + .from(targets) + .leftJoin(resources, eq(targets.resourceId, resources.resourceId)); + + let countQuery: any = db.select({ count: sql`cast(count(*) as integer)` }).from(targets); + + if (resourceId) { + baseQuery = baseQuery.where(eq(targets.resourceId, resourceId)); + countQuery = countQuery.where(eq(targets.resourceId, resourceId)); + } + + const targetsList = await baseQuery.limit(limit).offset(offset); + const totalCountResult = await countQuery; + const totalCount = totalCountResult[0].count; + + return res.status(HttpCode.OK).send( + response(res, { + data: { + targets: targetsList, + pagination: { + total: totalCount, + limit, + offset, + }, + }, + success: true, + error: false, + message: "Targets retrieved successfully", + status: HttpCode.OK, + }) + ); + } catch (error) { + next(error); + } +} \ No newline at end of file diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index 6b4d1213..1654264c 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -61,7 +61,7 @@ export async function updateTarget(req: Request, res: Response, next: NextFuncti } return res.status(HttpCode.OK).send( - response({ + response(res, { data: updatedTarget[0], success: true, error: false, diff --git a/server/routers/user/deleteUser.ts b/server/routers/user/deleteUser.ts index 8d193312..6a241ece 100644 --- a/server/routers/user/deleteUser.ts +++ b/server/routers/user/deleteUser.ts @@ -39,7 +39,7 @@ export async function deleteUser(req: Request, res: Response, next: NextFunction } return res.status(HttpCode.OK).send( - response({ + response(res, { data: null, success: true, error: false, diff --git a/server/routers/user/getUser.ts b/server/routers/user/getUser.ts index ff98392c..6286819d 100644 --- a/server/routers/user/getUser.ts +++ b/server/routers/user/getUser.ts @@ -43,7 +43,7 @@ export async function getUser(req: Request, res: Response, next: NextFunction): const { passwordHash: _, ...userWithoutPassword } = user[0]; return res.status(HttpCode.OK).send( - response({ + response(res, { data: userWithoutPassword, success: true, error: false, diff --git a/server/routers/user/listUsers.ts b/server/routers/user/listUsers.ts new file mode 100644 index 00000000..43d636a5 --- /dev/null +++ b/server/routers/user/listUsers.ts @@ -0,0 +1,61 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { users } from '@server/db/schema'; +import response from "@server/utils/response"; +import HttpCode from '@server/types/HttpCode'; +import createHttpError from 'http-errors'; +import { sql } from 'drizzle-orm'; + +const listUsersSchema = z.object({ + limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), + offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), +}); + +export async function listUsers(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedQuery = listUsersSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { limit, offset } = parsedQuery.data; + + const usersList = await db.select() + .from(users) + .limit(limit) + .offset(offset); + + const totalCountResult = await db + .select({ count: sql`cast(count(*) as integer)` }) + .from(users); + const totalCount = totalCountResult[0].count; + + // Remove passwordHash from each user object + const usersWithoutPassword = usersList.map(({ passwordHash, ...userWithoutPassword }) => userWithoutPassword); + + return res.status(HttpCode.OK).send( + response(res, { + data: { + users: usersWithoutPassword, + pagination: { + total: totalCount, + limit, + offset, + }, + }, + success: true, + error: false, + message: "Users retrieved successfully", + status: HttpCode.OK, + }) + ); + } catch (error) { + next(error); + } +} \ No newline at end of file From 54f94522bb772e11d1c7eb1243927b6a8d63535a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 2 Oct 2024 21:22:17 -0400 Subject: [PATCH 2/2] Add list endpoints to routers --- server/routers/external.ts | 5 +++++ server/routers/org/index.ts | 1 + server/routers/resource/index.ts | 1 + server/routers/site/index.ts | 1 + server/routers/target/index.ts | 1 + server/routers/user/index.ts | 1 + 6 files changed, 10 insertions(+) diff --git a/server/routers/external.ts b/server/routers/external.ts index d116e87f..a4b413a8 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -19,25 +19,30 @@ export const authenticated = Router(); authenticated.put("/site", site.createSite); authenticated.get("/site/:siteId", site.getSite); +authenticated.get("/sites", site.listSites); authenticated.post("/site/:siteId", site.updateSite); authenticated.delete("/site/:siteId", site.deleteSite); authenticated.put("/org", org.createOrg); authenticated.get("/org/:orgId", org.getOrg); +authenticated.get("/orgs", org.listOrgs); authenticated.post("/org/:orgId", org.updateOrg); authenticated.delete("/org/:orgId", org.deleteOrg); authenticated.put("/resource", resource.createResource); authenticated.get("/resource/:resourceId", resource.getResource); +authenticated.get("/resources", resource.listResources); authenticated.post("/resource/:resourceId", resource.updateResource); authenticated.delete("/resource/:resourceId", resource.deleteResource); authenticated.put("/target", target.createTarget); authenticated.get("/target/:targetId", target.getTarget); +authenticated.get("/targets", target.listTargets); authenticated.post("/target/:targetId", target.updateTarget); authenticated.delete("/target/:targetId", target.deleteTarget); authenticated.get("/user/:userId", user.getUser); +authenticated.get("/users", user.listUsers); authenticated.delete("/user/:userId", user.deleteUser); // Auth routes diff --git a/server/routers/org/index.ts b/server/routers/org/index.ts index 596fc93e..c8ea87b2 100644 --- a/server/routers/org/index.ts +++ b/server/routers/org/index.ts @@ -2,3 +2,4 @@ export * from "./getOrg"; export * from "./createOrg"; export * from "./deleteOrg"; export * from "./updateOrg"; +export * from "./listOrgs"; \ No newline at end of file diff --git a/server/routers/resource/index.ts b/server/routers/resource/index.ts index 2192284f..42033ea6 100644 --- a/server/routers/resource/index.ts +++ b/server/routers/resource/index.ts @@ -2,3 +2,4 @@ export * from "./getResource"; export * from "./createResource"; export * from "./deleteResource"; export * from "./updateResource"; +export * from "./listResources"; \ No newline at end of file diff --git a/server/routers/site/index.ts b/server/routers/site/index.ts index 2e7db75c..320c10de 100644 --- a/server/routers/site/index.ts +++ b/server/routers/site/index.ts @@ -2,3 +2,4 @@ export * from "./getSite"; export * from "./createSite"; export * from "./deleteSite"; export * from "./updateSite"; +export * from "./listSites"; \ No newline at end of file diff --git a/server/routers/target/index.ts b/server/routers/target/index.ts index 020f1e6c..b128edcd 100644 --- a/server/routers/target/index.ts +++ b/server/routers/target/index.ts @@ -2,3 +2,4 @@ export * from "./getTarget"; export * from "./createTarget"; export * from "./deleteTarget"; export * from "./updateTarget"; +export * from "./listTargets"; \ No newline at end of file diff --git a/server/routers/user/index.ts b/server/routers/user/index.ts index d6610fba..42478702 100644 --- a/server/routers/user/index.ts +++ b/server/routers/user/index.ts @@ -1,2 +1,3 @@ export * from "./getUser"; export * from "./deleteUser"; +export * from "./listUsers"; \ No newline at end of file