diff --git a/server/config.ts b/server/config.ts index 228072fb..665f3882 100644 --- a/server/config.ts +++ b/server/config.ts @@ -120,7 +120,8 @@ if (!parsedConfig.success) { throw new Error(`Invalid configuration file: ${errors}`); } -process.env.SERVER_EXTERNAL_PORT = parsedConfig.data.server.external_port.toString(); +process.env.SERVER_EXTERNAL_PORT = + parsedConfig.data.server.external_port.toString(); process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags ?.require_email_verification ? "true" diff --git a/server/routers/external.ts b/server/routers/external.ts index 1b13f058..d57327f9 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -47,7 +47,7 @@ authenticated.get("/org/:orgId/sites", verifyOrgAccess, site.listSites); authenticated.get("/org/:orgId/site/:niceId", verifyOrgAccess, site.getSite); authenticated.get( - "/org/:orgId/pickSiteDefaults", + "/org/:orgId/pick-site-defaults", verifyOrgAccess, site.pickSiteDefaults ); @@ -118,12 +118,12 @@ authenticated.delete( target.deleteTarget ); -authenticated.put( - "/org/:orgId/role", - verifyOrgAccess, - verifySuperuser, - role.createRole -); +// authenticated.put( +// "/org/:orgId/role", +// verifyOrgAccess, +// verifySuperuser, +// role.createRole +// ); authenticated.get("/org/:orgId/roles", verifyOrgAccess, role.listRoles); authenticated.get( "/role/:roleId", @@ -131,18 +131,18 @@ authenticated.get( verifyUserInRole, role.getRole ); -authenticated.post( - "/role/:roleId", - verifyRoleAccess, - verifySuperuser, - role.updateRole -); -authenticated.delete( - "/role/:roleId", - verifyRoleAccess, - verifySuperuser, - role.deleteRole -); +// authenticated.post( +// "/role/:roleId", +// verifyRoleAccess, +// verifySuperuser, +// role.updateRole +// ); +// authenticated.delete( +// "/role/:roleId", +// verifyRoleAccess, +// verifySuperuser, +// role.deleteRole +// ); authenticated.put( "/role/:roleId/site", @@ -210,12 +210,6 @@ authenticated.delete( verifyUserAccess, user.removeUserOrg ); -authenticated.put( - "/org/:orgId/user/:userId", - verifyOrgAccess, - verifyUserAccess, - user.addUserOrg -); authenticated.put( "/user/:userId/site", diff --git a/server/routers/gerbil/getConfig.ts b/server/routers/gerbil/getConfig.ts index 1eeb881c..a45806f0 100644 --- a/server/routers/gerbil/getConfig.ts +++ b/server/routers/gerbil/getConfig.ts @@ -10,6 +10,7 @@ import logger from '@server/logger'; import config from "@server/config"; import { getUniqueExitNodeEndpointName } from '@server/db/names'; import { findNextAvailableCidr } from "@server/utils/ip"; +import { fromError } from 'zod-validation-error'; // Define Zod schema for request validation const getConfigSchema = z.object({ publicKey: z.string(), @@ -33,7 +34,7 @@ export async function getConfig(req: Request, res: Response, next: NextFunction) return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } diff --git a/server/routers/org/checkId.ts b/server/routers/org/checkId.ts index 741738e8..7ab2fe58 100644 --- a/server/routers/org/checkId.ts +++ b/server/routers/org/checkId.ts @@ -1,32 +1,38 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { orgs } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { orgs } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const getOrgSchema = z.object({ - orgId: z.string() + orgId: z.string(), }); -export async function checkId(req: Request, res: Response, next: NextFunction): Promise { +export async function checkId( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedQuery = getOrgSchema.safeParse(req.query); if (!parsedQuery.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedQuery.error.errors.map(e => e.message).join(', ') + fromError(parsedQuery.error).toString() ) ); } const { orgId } = parsedQuery.data; - const org = await db.select() + const org = await db + .select() .from(orgs) .where(eq(orgs.orgId, orgId)) .limit(1); @@ -50,6 +56,11 @@ export async function checkId(req: Request, res: Response, next: NextFunction): }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index 2c504660..9f4854dc 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -10,6 +10,7 @@ import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import logger from '@server/logger'; import { createSuperuserRole } from '@server/db/ensureActions'; import config, { APP_PATH } from "@server/config"; +import { fromError } from 'zod-validation-error'; const createOrgSchema = z.object({ orgId: z.string(), @@ -26,7 +27,7 @@ export async function createOrg(req: Request, res: Response, next: NextFunction) return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 44a1a6e8..24f306aa 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -8,6 +8,7 @@ import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import logger from '@server/logger'; +import { fromError } from 'zod-validation-error'; const deleteOrgSchema = z.object({ orgId: z.string() @@ -20,7 +21,7 @@ export async function deleteOrg(req: Request, res: Response, next: NextFunction) return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } diff --git a/server/routers/org/updateOrg.ts b/server/routers/org/updateOrg.ts index 44f757cf..e721f641 100644 --- a/server/routers/org/updateOrg.ts +++ b/server/routers/org/updateOrg.ts @@ -8,6 +8,7 @@ import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import logger from '@server/logger'; +import { fromError } from 'zod-validation-error'; const updateOrgParamsSchema = z.object({ orgId: z.string() @@ -27,7 +28,7 @@ export async function updateOrg(req: Request, res: Response, next: NextFunction) return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -37,7 +38,7 @@ export async function updateOrg(req: Request, res: Response, next: NextFunction) return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index cb054243..77f28820 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -9,6 +9,7 @@ import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import logger from '@server/logger'; import { eq, and } from 'drizzle-orm'; import stoi from '@server/utils/stoi'; +import { fromError } from 'zod-validation-error'; const createResourceParamsSchema = z.object({ siteId: z.string().optional().transform(stoi).pipe(z.number().int().positive().optional()), @@ -29,7 +30,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -42,7 +43,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } diff --git a/server/routers/resource/deleteResource.ts b/server/routers/resource/deleteResource.ts index ffb15286..5fb70e17 100644 --- a/server/routers/resource/deleteResource.ts +++ b/server/routers/resource/deleteResource.ts @@ -1,20 +1,25 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; // Define Zod schema for request parameters validation const deleteResourceSchema = z.object({ resourceId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function deleteResource(req: Request, res: Response, next: NextFunction): Promise { +export async function deleteResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request parameters const parsedParams = deleteResourceSchema.safeParse(req.params); @@ -22,7 +27,7 @@ export async function deleteResource(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -30,13 +35,22 @@ export async function deleteResource(req: Request, res: Response, next: NextFunc const { resourceId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.deleteResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.deleteResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Delete the resource from the database - const deletedResource = await db.delete(resources) + const deletedResource = await db + .delete(resources) .where(eq(resources.resourceId, resourceId)) .returning(); @@ -58,6 +72,11 @@ export async function deleteResource(req: Request, res: Response, next: NextFunc }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index 21b361e0..122e9dbb 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; // Define Zod schema for request parameters validation const getResourceSchema = z.object({ @@ -19,9 +20,13 @@ export type GetResourceResponse = { siteId: number; orgId: string; name: string; -} +}; -export async function getResource(req: Request, res: Response, next: NextFunction): Promise { +export async function getResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request parameters const parsedParams = getResourceSchema.safeParse(req.params); @@ -29,7 +34,7 @@ export async function getResource(req: Request, res: Response, next: NextFunctio return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -37,13 +42,22 @@ export async function getResource(req: Request, res: Response, next: NextFunctio const { resourceId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.getResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.getResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Fetch the resource from the database - const resource = await db.select() + const resource = await db + .select() .from(resources) .where(eq(resources.resourceId, resourceId)) .limit(1); @@ -62,7 +76,7 @@ export async function getResource(req: Request, res: Response, next: NextFunctio resourceId: resource[0].resourceId, siteId: resource[0].siteId, orgId: resource[0].orgId, - name: resource[0].name + name: resource[0].name, }, success: true, error: false, @@ -71,6 +85,11 @@ export async function getResource(req: Request, res: Response, next: NextFunctio }); } catch (error) { throw error; - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/resource/listResourceRoles.ts b/server/routers/resource/listResourceRoles.ts index 4883be49..aa624733 100644 --- a/server/routers/resource/listResourceRoles.ts +++ b/server/routers/resource/listResourceRoles.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleResources, roles } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleResources, roles } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const listResourceRolesSchema = z.object({ resourceId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function listResourceRoles(req: Request, res: Response, next: NextFunction): Promise { +export async function listResourceRoles( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = listResourceRolesSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,9 +33,17 @@ export async function listResourceRoles(req: Request, res: Response, next: NextF const { resourceId } = parsedParams.data; // Check if the user has permission to list resource roles - const hasPermission = await checkUserActionPermission(ActionsEnum.listResourceRoles, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.listResourceRoles, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } const resourceRolesList = await db @@ -53,6 +66,11 @@ export async function listResourceRoles(req: Request, res: Response, next: NextF }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index a03ef573..5b208f9a 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; // Define Zod schema for request parameters validation const updateResourceParamsSchema = z.object({ @@ -15,14 +16,20 @@ const updateResourceParamsSchema = z.object({ }); // Define Zod schema for request body validation -const updateResourceBodySchema = z.object({ - name: z.string().min(1).max(255).optional(), - subdomain: z.string().min(1).max(255).optional(), -}).refine(data => Object.keys(data).length > 0, { - message: "At least one field must be provided for update" -}); +const updateResourceBodySchema = z + .object({ + name: z.string().min(1).max(255).optional(), + subdomain: z.string().min(1).max(255).optional(), + }) + .refine((data) => Object.keys(data).length > 0, { + message: "At least one field must be provided for update", + }); -export async function updateResource(req: Request, res: Response, next: NextFunction): Promise { +export async function updateResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request parameters const parsedParams = updateResourceParamsSchema.safeParse(req.params); @@ -30,7 +37,7 @@ export async function updateResource(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -41,7 +48,7 @@ export async function updateResource(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -50,13 +57,22 @@ export async function updateResource(req: Request, res: Response, next: NextFunc const updateData = parsedBody.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.updateResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.updateResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Update the resource in the database - const updatedResource = await db.update(resources) + const updatedResource = await db + .update(resources) .set(updateData) .where(eq(resources.resourceId, resourceId)) .returning(); @@ -79,6 +95,11 @@ export async function updateResource(req: Request, res: Response, next: NextFunc }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/role/addRoleAction.ts b/server/routers/role/addRoleAction.ts index 5e677aa4..99944b89 100644 --- a/server/routers/role/addRoleAction.ts +++ b/server/routers/role/addRoleAction.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleActions, roles } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleActions, roles } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { eq } from 'drizzle-orm'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { eq } from "drizzle-orm"; +import { fromError } from "zod-validation-error"; const addRoleActionParamSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -17,14 +18,18 @@ const addRoleActionSchema = z.object({ actionId: z.string(), }); -export async function addRoleAction(req: Request, res: Response, next: NextFunction): Promise { +export async function addRoleAction( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addRoleActionSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function addRoleAction(req: Request, res: Response, next: NextFunct return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -44,22 +49,42 @@ export async function addRoleAction(req: Request, res: Response, next: NextFunct const { roleId } = parsedParams.data; // Check if the user has permission to add role actions - const hasPermission = await checkUserActionPermission(ActionsEnum.addRoleAction, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addRoleAction, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Get the orgId for the role - const role = await db.select({ orgId: roles.orgId }).from(roles).where(eq(roles.roleId, roleId)).limit(1); + const role = await db + .select({ orgId: roles.orgId }) + .from(roles) + .where(eq(roles.roleId, roleId)) + .limit(1); if (role.length === 0) { - return next(createHttpError(HttpCode.NOT_FOUND, `Role with ID ${roleId} not found`)); + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Role with ID ${roleId} not found` + ) + ); } - const newRoleAction = await db.insert(roleActions).values({ - roleId, - actionId, - orgId: role[0].orgId!, - }).returning(); + const newRoleAction = await db + .insert(roleActions) + .values({ + roleId, + actionId, + orgId: role[0].orgId!, + }) + .returning(); return response(res, { data: newRoleAction[0], @@ -70,6 +95,11 @@ export async function addRoleAction(req: Request, res: Response, next: NextFunct }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/addRoleResource.ts b/server/routers/role/addRoleResource.ts index 93bd244f..59091050 100644 --- a/server/routers/role/addRoleResource.ts +++ b/server/routers/role/addRoleResource.ts @@ -1,12 +1,13 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleResources } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleResources } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const addRoleResourceParamsSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -16,14 +17,18 @@ const addRoleResourceSchema = z.object({ resourceId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function addRoleResource(req: Request, res: Response, next: NextFunction): Promise { +export async function addRoleResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addRoleResourceSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -35,7 +40,7 @@ export async function addRoleResource(req: Request, res: Response, next: NextFun return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -43,15 +48,26 @@ export async function addRoleResource(req: Request, res: Response, next: NextFun const { roleId } = parsedParams.data; // Check if the user has permission to add role resources - const hasPermission = await checkUserActionPermission(ActionsEnum.addRoleResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addRoleResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const newRoleResource = await db.insert(roleResources).values({ - roleId, - resourceId, - }).returning(); + const newRoleResource = await db + .insert(roleResources) + .values({ + roleId, + resourceId, + }) + .returning(); return response(res, { data: newRoleResource[0], @@ -62,6 +78,11 @@ export async function addRoleResource(req: Request, res: Response, next: NextFun }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/addRoleSite.ts b/server/routers/role/addRoleSite.ts index 7ac55e56..3d21149e 100644 --- a/server/routers/role/addRoleSite.ts +++ b/server/routers/role/addRoleSite.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources, roleResources, roleSites } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources, roleResources, roleSites } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { eq } from 'drizzle-orm'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { eq } from "drizzle-orm"; +import { fromError } from "zod-validation-error"; const addRoleSiteParamsSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -17,14 +18,18 @@ const addRoleSiteSchema = z.object({ siteId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function addRoleSite(req: Request, res: Response, next: NextFunction): Promise { +export async function addRoleSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addRoleSiteSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function addRoleSite(req: Request, res: Response, next: NextFunctio return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -44,17 +49,29 @@ export async function addRoleSite(req: Request, res: Response, next: NextFunctio const { roleId } = parsedParams.data; // Check if the user has permission to add role sites - const hasPermission = await checkUserActionPermission(ActionsEnum.addRoleSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addRoleSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const newRoleSite = await db.insert(roleSites).values({ - roleId, - siteId, - }).returning(); + const newRoleSite = await db + .insert(roleSites) + .values({ + roleId, + siteId, + }) + .returning(); - const siteResources = await db.select() + const siteResources = await db + .select() .from(resources) .where(eq(resources.siteId, siteId)); @@ -74,6 +91,11 @@ export async function addRoleSite(req: Request, res: Response, next: NextFunctio }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/createRole.ts b/server/routers/role/createRole.ts index 01d9af2d..f61e8c07 100644 --- a/server/routers/role/createRole.ts +++ b/server/routers/role/createRole.ts @@ -1,15 +1,16 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roles } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roles } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const createRoleParamsSchema = z.object({ - orgId: z.string() + orgId: z.string(), }); const createRoleSchema = z.object({ @@ -17,14 +18,18 @@ const createRoleSchema = z.object({ description: z.string().optional(), }); -export async function createRole(req: Request, res: Response, next: NextFunction): Promise { +export async function createRole( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = createRoleSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function createRole(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -44,15 +49,26 @@ export async function createRole(req: Request, res: Response, next: NextFunction const { orgId } = parsedParams.data; // Check if the user has permission to create roles - const hasPermission = await checkUserActionPermission(ActionsEnum.createRole, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.createRole, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const newRole = await db.insert(roles).values({ - ...roleData, - orgId, - }).returning(); + const newRole = await db + .insert(roles) + .values({ + ...roleData, + orgId, + }) + .returning(); return response(res, { data: newRole[0], @@ -63,6 +79,11 @@ export async function createRole(req: Request, res: Response, next: NextFunction }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/deleteRole.ts b/server/routers/role/deleteRole.ts index f5b1225a..62138e53 100644 --- a/server/routers/role/deleteRole.ts +++ b/server/routers/role/deleteRole.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roles } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roles } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const deleteRoleSchema = z.object({ - roleId: z.string().transform(Number).pipe(z.number().int().positive()) + roleId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function deleteRole(req: Request, res: Response, next: NextFunction): Promise { +export async function deleteRole( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = deleteRoleSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,15 +33,24 @@ export async function deleteRole(req: Request, res: Response, next: NextFunction const { roleId } = parsedParams.data; // Check if the user has permission to delete roles - const hasPermission = await checkUserActionPermission(ActionsEnum.deleteRole, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.deleteRole, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const role = await db.select() - .from(roles) - .where(eq(roles.roleId, roleId)) - .limit(1); + const role = await db + .select() + .from(roles) + .where(eq(roles.roleId, roleId)) + .limit(1); if (role.length === 0) { return next( @@ -56,7 +70,8 @@ export async function deleteRole(req: Request, res: Response, next: NextFunction ); } - const deletedRole = await db.delete(roles) + const deletedRole = await db + .delete(roles) .where(eq(roles.roleId, roleId)) .returning(); @@ -78,6 +93,11 @@ export async function deleteRole(req: Request, res: Response, next: NextFunction }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/getRole.ts b/server/routers/role/getRole.ts index 6866448b..964661c7 100644 --- a/server/routers/role/getRole.ts +++ b/server/routers/role/getRole.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roles } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roles } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const getRoleSchema = z.object({ - roleId: z.string().transform(Number).pipe(z.number().int().positive()) + roleId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function getRole(req: Request, res: Response, next: NextFunction): Promise { +export async function getRole( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = getRoleSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,12 +33,21 @@ export async function getRole(req: Request, res: Response, next: NextFunction): const { roleId } = parsedParams.data; // Check if the user has permission to get roles - const hasPermission = await checkUserActionPermission(ActionsEnum.getRole, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.getRole, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const role = await db.select() + const role = await db + .select() .from(roles) .where(eq(roles.roleId, roleId)) .limit(1); @@ -56,6 +70,11 @@ export async function getRole(req: Request, res: Response, next: NextFunction): }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/listRoleActions.ts b/server/routers/role/listRoleActions.ts index 6f16b956..0a61070f 100644 --- a/server/routers/role/listRoleActions.ts +++ b/server/routers/role/listRoleActions.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleActions, actions } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleActions, actions } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const listRoleActionsSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function listRoleActions(req: Request, res: Response, next: NextFunction): Promise { +export async function listRoleActions( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = listRoleActionsSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,9 +33,17 @@ export async function listRoleActions(req: Request, res: Response, next: NextFun const { roleId } = parsedParams.data; // Check if the user has permission to list role actions - const hasPermission = await checkUserActionPermission(ActionsEnum.listRoleActions, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.listRoleActions, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } const roleActionsList = await db @@ -43,7 +56,7 @@ export async function listRoleActions(req: Request, res: Response, next: NextFun .innerJoin(actions, eq(roleActions.actionId, actions.actionId)) .where(eq(roleActions.roleId, roleId)); - // TODO: Do we need to filter out what the user can see? + // TODO: Do we need to filter out what the user can see? return response(res, { data: roleActionsList, @@ -54,6 +67,11 @@ export async function listRoleActions(req: Request, res: Response, next: NextFun }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/listRoleResources.ts b/server/routers/role/listRoleResources.ts index 0d4644a4..18075e44 100644 --- a/server/routers/role/listRoleResources.ts +++ b/server/routers/role/listRoleResources.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleResources, resources } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleResources, resources } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const listRoleResourcesSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function listRoleResources(req: Request, res: Response, next: NextFunction): Promise { +export async function listRoleResources( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = listRoleResourcesSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,9 +33,17 @@ export async function listRoleResources(req: Request, res: Response, next: NextF const { roleId } = parsedParams.data; // Check if the user has permission to list role resources - const hasPermission = await checkUserActionPermission(ActionsEnum.listRoleResources, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.listRoleResources, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } const roleResourcesList = await db @@ -40,10 +53,13 @@ export async function listRoleResources(req: Request, res: Response, next: NextF subdomain: resources.subdomain, }) .from(roleResources) - .innerJoin(resources, eq(roleResources.resourceId, resources.resourceId)) + .innerJoin( + resources, + eq(roleResources.resourceId, resources.resourceId) + ) .where(eq(roleResources.roleId, roleId)); - // TODO: Do we need to filter out what the user can see? + // TODO: Do we need to filter out what the user can see? return response(res, { data: roleResourcesList, @@ -54,6 +70,11 @@ export async function listRoleResources(req: Request, res: Response, next: NextF }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/listRoleSites.ts b/server/routers/role/listRoleSites.ts index 8b11d036..e74a50f9 100644 --- a/server/routers/role/listRoleSites.ts +++ b/server/routers/role/listRoleSites.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleSites, sites } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleSites, sites } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const listRoleSitesSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function listRoleSites(req: Request, res: Response, next: NextFunction): Promise { +export async function listRoleSites( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = listRoleSitesSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,9 +33,17 @@ export async function listRoleSites(req: Request, res: Response, next: NextFunct const { roleId } = parsedParams.data; // Check if the user has permission to list role sites - const hasPermission = await checkUserActionPermission(ActionsEnum.listRoleSites, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.listRoleSites, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } const roleSitesList = await db @@ -42,7 +55,7 @@ export async function listRoleSites(req: Request, res: Response, next: NextFunct .innerJoin(sites, eq(roleSites.siteId, sites.siteId)) .where(eq(roleSites.roleId, roleId)); - // TODO: Do we need to filter out what the user can see? + // TODO: Do we need to filter out what the user can see? return response(res, { data: roleSitesList, @@ -53,6 +66,11 @@ export async function listRoleSites(req: Request, res: Response, next: NextFunct }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/listRoles.ts b/server/routers/role/listRoles.ts index db489a47..1b6f911f 100644 --- a/server/routers/role/listRoles.ts +++ b/server/routers/role/listRoles.ts @@ -1,32 +1,49 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roles, orgs } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roles, orgs } 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'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { sql, eq } from "drizzle-orm"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const listRolesParamsSchema = z.object({ - orgId: z.string() + orgId: z.string(), }); const listRolesSchema = 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()), + 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 listRoles(req: Request, res: Response, next: NextFunction): Promise { +export async function listRoles( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedQuery = listRolesSchema.safeParse(req.query); if (!parsedQuery.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedQuery.error.errors.map(e => e.message).join(', ') + fromError(parsedQuery.error).toString() ) ); } @@ -38,7 +55,7 @@ export async function listRoles(req: Request, res: Response, next: NextFunction) return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -46,9 +63,17 @@ export async function listRoles(req: Request, res: Response, next: NextFunction) const { orgId } = parsedParams.data; // Check if the user has permission to list roles - const hasPermission = await checkUserActionPermission(ActionsEnum.listRoles, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.listRoles, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } let baseQuery: any = db @@ -64,8 +89,10 @@ export async function listRoles(req: Request, res: Response, next: NextFunction) .leftJoin(orgs, eq(roles.orgId, orgs.orgId)) .where(eq(roles.orgId, orgId)); - let countQuery: any = db.select({ count: sql`cast(count(*) as integer)` }).from(roles) - .where(eq(roles.orgId, orgId)); + let countQuery: any = db + .select({ count: sql`cast(count(*) as integer)` }) + .from(roles) + .where(eq(roles.orgId, orgId)); const rolesList = await baseQuery.limit(limit).offset(offset); const totalCountResult = await countQuery; @@ -87,6 +114,11 @@ export async function listRoles(req: Request, res: Response, next: NextFunction) }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/removeRoleAction.ts b/server/routers/role/removeRoleAction.ts index 08c1217d..a9f071b5 100644 --- a/server/routers/role/removeRoleAction.ts +++ b/server/routers/role/removeRoleAction.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleActions } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleActions } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeRoleActionParamsSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -17,14 +18,18 @@ const removeRoleActionSchema = z.object({ actionId: z.string(), }); -export async function removeRoleAction(req: Request, res: Response, next: NextFunction): Promise { +export async function removeRoleAction( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeRoleActionSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function removeRoleAction(req: Request, res: Response, next: NextFu return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -44,13 +49,27 @@ export async function removeRoleAction(req: Request, res: Response, next: NextFu const { roleId } = parsedBody.data; // Check if the user has permission to remove role actions - const hasPermission = await checkUserActionPermission(ActionsEnum.removeRoleAction, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeRoleAction, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const deletedRoleAction = await db.delete(roleActions) - .where(and(eq(roleActions.roleId, roleId), eq(roleActions.actionId, actionId))) + const deletedRoleAction = await db + .delete(roleActions) + .where( + and( + eq(roleActions.roleId, roleId), + eq(roleActions.actionId, actionId) + ) + ) .returning(); if (deletedRoleAction.length === 0) { @@ -71,6 +90,11 @@ export async function removeRoleAction(req: Request, res: Response, next: NextFu }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/removeRoleResource.ts b/server/routers/role/removeRoleResource.ts index 3ec19263..bf2582c3 100644 --- a/server/routers/role/removeRoleResource.ts +++ b/server/routers/role/removeRoleResource.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleResources } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleResources } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeRoleResourceParamsSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -17,14 +18,18 @@ const removeRoleResourceSchema = z.object({ resourceId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function removeRoleResource(req: Request, res: Response, next: NextFunction): Promise { +export async function removeRoleResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeRoleResourceSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function removeRoleResource(req: Request, res: Response, next: Next return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -44,13 +49,27 @@ export async function removeRoleResource(req: Request, res: Response, next: Next const { roleId } = parsedBody.data; // Check if the user has permission to remove role resources - const hasPermission = await checkUserActionPermission(ActionsEnum.removeRoleResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeRoleResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const deletedRoleResource = await db.delete(roleResources) - .where(and(eq(roleResources.roleId, roleId), eq(roleResources.resourceId, resourceId))) + const deletedRoleResource = await db + .delete(roleResources) + .where( + and( + eq(roleResources.roleId, roleId), + eq(roleResources.resourceId, resourceId) + ) + ) .returning(); if (deletedRoleResource.length === 0) { @@ -71,6 +90,11 @@ export async function removeRoleResource(req: Request, res: Response, next: Next }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/removeRoleSite.ts b/server/routers/role/removeRoleSite.ts index badaac87..dd1f2d57 100644 --- a/server/routers/role/removeRoleSite.ts +++ b/server/routers/role/removeRoleSite.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources, roleResources, roleSites } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources, roleResources, roleSites } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeRoleSiteParamsSchema = z.object({ roleId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -17,14 +18,18 @@ const removeRoleSiteSchema = z.object({ siteId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function removeRoleSite(req: Request, res: Response, next: NextFunction): Promise { +export async function removeRoleSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeRoleSiteSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function removeRoleSite(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -44,13 +49,24 @@ export async function removeRoleSite(req: Request, res: Response, next: NextFunc const { roleId } = parsedBody.data; // Check if the user has permission to remove role sites - const hasPermission = await checkUserActionPermission(ActionsEnum.removeRoleSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeRoleSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const deletedRoleSite = await db.delete(roleSites) - .where(and(eq(roleSites.roleId, roleId), eq(roleSites.siteId, siteId))) + const deletedRoleSite = await db + .delete(roleSites) + .where( + and(eq(roleSites.roleId, roleId), eq(roleSites.siteId, siteId)) + ) .returning(); if (deletedRoleSite.length === 0) { @@ -62,13 +78,20 @@ export async function removeRoleSite(req: Request, res: Response, next: NextFunc ); } - const siteResources = await db.select() - .from(resources) - .where(eq(resources.siteId, siteId)); + const siteResources = await db + .select() + .from(resources) + .where(eq(resources.siteId, siteId)); for (const resource of siteResources) { - await db.delete(roleResources) - .where(and(eq(roleResources.roleId, roleId), eq(roleResources.resourceId, resource.resourceId))) + await db + .delete(roleResources) + .where( + and( + eq(roleResources.roleId, roleId), + eq(roleResources.resourceId, resource.resourceId) + ) + ) .returning(); } @@ -81,6 +104,11 @@ export async function removeRoleSite(req: Request, res: Response, next: NextFunc }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/role/updateRole.ts b/server/routers/role/updateRole.ts index bd8960b5..8d05db76 100644 --- a/server/routers/role/updateRole.ts +++ b/server/routers/role/updateRole.ts @@ -1,33 +1,40 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roles } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roles } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const updateRoleParamsSchema = z.object({ - roleId: z.string().transform(Number).pipe(z.number().int().positive()) + roleId: z.string().transform(Number).pipe(z.number().int().positive()), }); -const updateRoleBodySchema = z.object({ - name: z.string().min(1).max(255).optional(), - description: z.string().optional(), -}).refine(data => Object.keys(data).length > 0, { - message: "At least one field must be provided for update" -}); +const updateRoleBodySchema = z + .object({ + name: z.string().min(1).max(255).optional(), + description: z.string().optional(), + }) + .refine((data) => Object.keys(data).length > 0, { + message: "At least one field must be provided for update", + }); -export async function updateRole(req: Request, res: Response, next: NextFunction): Promise { +export async function updateRole( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = updateRoleParamsSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -37,7 +44,7 @@ export async function updateRole(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -46,15 +53,24 @@ export async function updateRole(req: Request, res: Response, next: NextFunction const updateData = parsedBody.data; // Check if the user has permission to update roles - const hasPermission = await checkUserActionPermission(ActionsEnum.updateRole, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.updateRole, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const role = await db.select() - .from(roles) - .where(eq(roles.roleId, roleId)) - .limit(1); + const role = await db + .select() + .from(roles) + .where(eq(roles.roleId, roleId)) + .limit(1); if (role.length === 0) { return next( @@ -74,7 +90,8 @@ export async function updateRole(req: Request, res: Response, next: NextFunction ); } - const updatedRole = await db.update(roles) + const updatedRole = await db + .update(roles) .set(updateData) .where(eq(roles.roleId, roleId)) .returning(); @@ -97,6 +114,11 @@ export async function updateRole(req: Request, res: Response, next: NextFunction }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index ee8759ea..c50018ec 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -1,20 +1,25 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roles, userSites, sites, roleSites, exitNodes } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { + roles, + userSites, + sites, + roleSites, + exitNodes, +} from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { eq, and } from 'drizzle-orm'; -import { getUniqueSiteName } from '@server/db/names'; -import { addPeer } from '../gerbil/peers'; - -const API_BASE_URL = "http://localhost:3000"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { eq, and } from "drizzle-orm"; +import { getUniqueSiteName } from "@server/db/names"; +import { addPeer } from "../gerbil/peers"; +import { fromError } from "zod-validation-error"; const createSiteParamsSchema = z.object({ - orgId: z.string() + orgId: z.string(), }); // Define Zod schema for request body validation @@ -36,7 +41,11 @@ export type CreateSiteResponse = { // subnet: string; }; -export async function createSite(req: Request, res: Response, next: NextFunction): Promise { +export async function createSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request body const parsedBody = createSiteSchema.safeParse(req.body); @@ -44,7 +53,7 @@ export async function createSite(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -57,7 +66,7 @@ export async function createSite(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -65,38 +74,49 @@ export async function createSite(req: Request, res: Response, next: NextFunction const { orgId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.createSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.createSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission perform this action" + ) + ); } if (!req.userOrgRoleId) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have a role')); + return next( + createHttpError(HttpCode.FORBIDDEN, "User does not have a role") + ); } const niceId = await getUniqueSiteName(orgId); // Create new site in the database - const [newSite] = await db.insert(sites).values({ - orgId, - exitNodeId, - name, - niceId, - pubKey, - subnet, - }).returning(); + const [newSite] = await db + .insert(sites) + .values({ + orgId, + exitNodeId, + name, + niceId, + pubKey, + subnet, + }) + .returning(); // find the superuser roleId and also add the resource to the superuser role - const superuserRole = await db.select() - .from(roles) - .where(and(eq(roles.isSuperuserRole, true), eq(roles.orgId, orgId))) - .limit(1); + const superuserRole = await db + .select() + .from(roles) + .where(and(eq(roles.isSuperuserRole, true), eq(roles.orgId, orgId))) + .limit(1); if (superuserRole.length === 0) { return next( - createHttpError( - HttpCode.NOT_FOUND, - `Superuser role not found` - ) + createHttpError(HttpCode.NOT_FOUND, `Superuser role not found`) ); } @@ -135,6 +155,11 @@ export async function createSite(req: Request, res: Response, next: NextFunction }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index 399e7d0d..aa69ef44 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -1,23 +1,28 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { sites } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { sites } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { deletePeer } from '../gerbil/peers'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { deletePeer } from "../gerbil/peers"; +import { fromError } from "zod-validation-error"; const API_BASE_URL = "http://localhost:3000"; // Define Zod schema for request parameters validation const deleteSiteSchema = z.object({ - siteId: z.string().transform(Number).pipe(z.number().int().positive()) + siteId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function deleteSite(req: Request, res: Response, next: NextFunction): Promise { +export async function deleteSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request parameters const parsedParams = deleteSiteSchema.safeParse(req.params); @@ -25,7 +30,7 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -33,13 +38,22 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction const { siteId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.deleteSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.deleteSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Delete the site from the database - const [deletedSite] = await db.delete(sites) + const [deletedSite] = await db + .delete(sites) .where(eq(sites.siteId, siteId)) .returning(); @@ -63,26 +77,33 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } - async function removePeer(publicKey: string) { try { - const response = await fetch(`${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, { - method: 'DELETE', - }); + const response = await fetch( + `${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, + { + method: "DELETE", + } + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); - console.log('Peer removed successfully:', data.status); + console.log("Peer removed successfully:", data.status); return data; } catch (error: any) { - console.error('Error removing peer:', error.message); + console.error("Error removing peer:", error.message); throw error; } } diff --git a/server/routers/site/getSite.ts b/server/routers/site/getSite.ts index 927a3628..4791970b 100644 --- a/server/routers/site/getSite.ts +++ b/server/routers/site/getSite.ts @@ -1,18 +1,24 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { sites } from '@server/db/schema'; -import { eq, and } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { sites } from "@server/db/schema"; +import { eq, and } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import stoi from '@server/utils/stoi'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import stoi from "@server/utils/stoi"; +import { fromError } from "zod-validation-error"; // Define Zod schema for request parameters validation const getSiteSchema = z.object({ - siteId: z.string().optional().transform(stoi).pipe(z.number().int().positive().optional()).optional(), + siteId: z + .string() + .optional() + .transform(stoi) + .pipe(z.number().int().positive().optional()) + .optional(), niceId: z.string().optional(), orgId: z.string().optional(), }); @@ -22,9 +28,13 @@ export type GetSiteResponse = { name: string; subdomain: string; subnet: string; -} +}; -export async function getSite(req: Request, res: Response, next: NextFunction): Promise { +export async function getSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request parameters const parsedParams = getSiteSchema.safeParse(req.params); @@ -32,7 +42,7 @@ export async function getSite(req: Request, res: Response, next: NextFunction): return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -40,27 +50,37 @@ export async function getSite(req: Request, res: Response, next: NextFunction): const { siteId, niceId, orgId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.updateSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.updateSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } let site; // Fetch the site from the database if (siteId) { - site = await db.select() + site = await db + .select() .from(sites) .where(eq(sites.siteId, siteId)) .limit(1); } else if (niceId && orgId) { - site = await db.select() + site = await db + .select() .from(sites) .where(and(eq(sites.niceId, niceId), eq(sites.orgId, orgId))) .limit(1); } if (!site) { - return next(createHttpError(HttpCode.NOT_FOUND, 'Site not found')); + return next(createHttpError(HttpCode.NOT_FOUND, "Site not found")); } if (site.length === 0) { @@ -86,6 +106,11 @@ export async function getSite(req: Request, res: Response, next: NextFunction): }); } catch (error) { logger.error("Error from getSite: ", error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/site/listSiteRoles.ts b/server/routers/site/listSiteRoles.ts index 4acf52c0..8c725908 100644 --- a/server/routers/site/listSiteRoles.ts +++ b/server/routers/site/listSiteRoles.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { roleSites, roles } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { roleSites, roles } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const listSiteRolesSchema = z.object({ siteId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function listSiteRoles(req: Request, res: Response, next: NextFunction): Promise { +export async function listSiteRoles( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = listSiteRolesSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,9 +33,17 @@ export async function listSiteRoles(req: Request, res: Response, next: NextFunct const { siteId } = parsedParams.data; // Check if the user has permission to list site roles - const hasPermission = await checkUserActionPermission(ActionsEnum.listSiteRoles, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.listSiteRoles, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } const siteRolesList = await db @@ -53,6 +66,11 @@ export async function listSiteRoles(req: Request, res: Response, next: NextFunct }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/site/pickSiteDefaults.ts b/server/routers/site/pickSiteDefaults.ts index 5901de8c..ad369536 100644 --- a/server/routers/site/pickSiteDefaults.ts +++ b/server/routers/site/pickSiteDefaults.ts @@ -18,26 +18,25 @@ export type PickSiteDefaultsResponse = { listenPort: number; endpoint: string; subnet: string; -} +}; export async function pickSiteDefaults( req: Request, res: Response, - next: NextFunction, + next: NextFunction ): Promise { try { - // Check if the user has permission to list sites const hasPermission = await checkUserActionPermission( ActionsEnum.createSite, - req, + req ); if (!hasPermission) { return next( createHttpError( HttpCode.FORBIDDEN, - "User does not have permission to perform this action", - ), + "User does not have permission to perform this action" + ) ); } @@ -47,10 +46,7 @@ export async function pickSiteDefaults( const nodes = await db.select().from(exitNodes); if (nodes.length === 0) { return next( - createHttpError( - HttpCode.NOT_FOUND, - "No exit nodes available", - ), + createHttpError(HttpCode.NOT_FOUND, "No exit nodes available") ); } @@ -59,9 +55,10 @@ export async function pickSiteDefaults( // TODO: this probably can be optimized... // list all of the sites on that exit node - const sitesQuery = await db.select({ - subnet: sites.subnet - }) + const sitesQuery = await db + .select({ + subnet: sites.subnet, + }) .from(sites) .where(eq(sites.exitNodeId, exitNode.exitNodeId)); @@ -74,8 +71,8 @@ export async function pickSiteDefaults( return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "No available subnets", - ), + "No available subnets" + ) ); } @@ -97,11 +94,7 @@ export async function pickSiteDefaults( } catch (error) { logger.error(error); return next( - createHttpError( - HttpCode.INTERNAL_SERVER_ERROR, - "An error occurred...", - ), + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } } - diff --git a/server/routers/site/updateSite.ts b/server/routers/site/updateSite.ts index e5ecc409..52a39911 100644 --- a/server/routers/site/updateSite.ts +++ b/server/routers/site/updateSite.ts @@ -1,33 +1,40 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { sites } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { sites } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; // Define Zod schema for request parameters validation const updateSiteParamsSchema = z.object({ - siteId: z.string().transform(Number).pipe(z.number().int().positive()) + siteId: z.string().transform(Number).pipe(z.number().int().positive()), }); // Define Zod schema for request body validation -const updateSiteBodySchema = z.object({ - name: z.string().min(1).max(255).optional(), - subdomain: z.string().min(1).max(255).optional(), - pubKey: z.string().optional(), - subnet: z.string().optional(), - exitNode: z.number().int().positive().optional(), - megabytesIn: z.number().int().nonnegative().optional(), - megabytesOut: z.number().int().nonnegative().optional(), -}).refine(data => Object.keys(data).length > 0, { - message: "At least one field must be provided for update" -}); +const updateSiteBodySchema = z + .object({ + name: z.string().min(1).max(255).optional(), + subdomain: z.string().min(1).max(255).optional(), + pubKey: z.string().optional(), + subnet: z.string().optional(), + exitNode: z.number().int().positive().optional(), + megabytesIn: z.number().int().nonnegative().optional(), + megabytesOut: z.number().int().nonnegative().optional(), + }) + .refine((data) => Object.keys(data).length > 0, { + message: "At least one field must be provided for update", + }); -export async function updateSite(req: Request, res: Response, next: NextFunction): Promise { +export async function updateSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { // Validate request parameters const parsedParams = updateSiteParamsSchema.safeParse(req.params); @@ -35,7 +42,7 @@ export async function updateSite(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -46,7 +53,7 @@ export async function updateSite(req: Request, res: Response, next: NextFunction return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -55,13 +62,22 @@ export async function updateSite(req: Request, res: Response, next: NextFunction const updateData = parsedBody.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.updateSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.updateSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Update the site in the database - const updatedSite = await db.update(sites) + const updatedSite = await db + .update(sites) .set(updateData) .where(eq(sites.siteId, siteId)) .returning(); @@ -84,6 +100,11 @@ export async function updateSite(req: Request, res: Response, next: NextFunction }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 58344219..84f45f0e 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -1,15 +1,16 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources, sites, targets } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources, sites, targets } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { addPeer } from '../gerbil/peers'; -import { eq, and } from 'drizzle-orm'; -import { isIpInCidr } from '@server/utils/ip'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { addPeer } from "../gerbil/peers"; +import { eq, and } from "drizzle-orm"; +import { isIpInCidr } from "@server/utils/ip"; +import { fromError } from "zod-validation-error"; const createTargetParamsSchema = z.object({ resourceId: z.string().transform(Number).pipe(z.number().int().positive()), @@ -23,14 +24,18 @@ const createTargetSchema = z.object({ enabled: z.boolean().default(true), }); -export async function createTarget(req: Request, res: Response, next: NextFunction): Promise { +export async function createTarget( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = createTargetSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -42,7 +47,7 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -50,15 +55,24 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti const { resourceId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.createTarget, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.createTarget, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // get the resource - const [resource] = await db.select({ - siteId: resources.siteId, - }) + const [resource] = await db + .select({ + siteId: resources.siteId, + }) .from(resources) .where(eq(resources.resourceId, resourceId)); @@ -72,9 +86,10 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti } // TODO: is this all inefficient? - + // get the site - const [site] = await db.select() + const [site] = await db + .select() .from(sites) .where(eq(sites.siteId, resource.siteId!)) .limit(1); @@ -98,10 +113,13 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti ); } - const newTarget = await db.insert(targets).values({ - resourceId, - ...targetData - }).returning(); + const newTarget = await db + .insert(targets) + .values({ + resourceId, + ...targetData, + }) + .returning(); // Fetch resources for this site const resourcesRes = await db.query.resources.findMany({ @@ -109,16 +127,18 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti }); // Fetch targets for all resources of this site - const targetIps = await Promise.all(resourcesRes.map(async (resource) => { - const targetsRes = await db.query.targets.findMany({ - where: eq(targets.resourceId, resource.resourceId), - }); - return targetsRes.map(target => `${target.ip}/32`); - })); + const targetIps = await Promise.all( + resourcesRes.map(async (resource) => { + const targetsRes = await db.query.targets.findMany({ + where: eq(targets.resourceId, resource.resourceId), + }); + return targetsRes.map((target) => `${target.ip}/32`); + }) + ); await addPeer(site.exitNodeId!, { publicKey: site.pubKey, - allowedIps: targetIps.flat() + allowedIps: targetIps.flat(), }); return response(res, { @@ -130,6 +150,11 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/target/deleteTarget.ts b/server/routers/target/deleteTarget.ts index f74b7bbe..e5e50ffd 100644 --- a/server/routers/target/deleteTarget.ts +++ b/server/routers/target/deleteTarget.ts @@ -1,27 +1,32 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources, sites, targets } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources, sites, targets } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { addPeer } from '../gerbil/peers'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { addPeer } from "../gerbil/peers"; +import { fromError } from "zod-validation-error"; const deleteTargetSchema = z.object({ - targetId: z.string().transform(Number).pipe(z.number().int().positive()) + targetId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function deleteTarget(req: Request, res: Response, next: NextFunction): Promise { +export async function deleteTarget( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = deleteTargetSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -29,13 +34,21 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti const { targetId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.deleteTarget, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.deleteTarget, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - - const [deletedTarget] = await db.delete(targets) + const [deletedTarget] = await db + .delete(targets) .where(eq(targets.targetId, targetId)) .returning(); @@ -48,9 +61,10 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti ); } // get the resource - const [resource] = await db.select({ - siteId: resources.siteId, - }) + const [resource] = await db + .select({ + siteId: resources.siteId, + }) .from(resources) .where(eq(resources.resourceId, deletedTarget.resourceId!)); @@ -66,7 +80,8 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti // TODO: is this all inefficient? // get the site - const [site] = await db.select() + const [site] = await db + .select() .from(sites) .where(eq(sites.siteId, resource.siteId!)) .limit(1); @@ -86,16 +101,18 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti }); // Fetch targets for all resources of this site - const targetIps = await Promise.all(resourcesRes.map(async (resource) => { - const targetsRes = await db.query.targets.findMany({ - where: eq(targets.resourceId, resource.resourceId), - }); - return targetsRes.map(target => `${target.ip}/32`); - })); + const targetIps = await Promise.all( + resourcesRes.map(async (resource) => { + const targetsRes = await db.query.targets.findMany({ + where: eq(targets.resourceId, resource.resourceId), + }); + return targetsRes.map((target) => `${target.ip}/32`); + }) + ); await addPeer(site.exitNodeId!, { publicKey: site.pubKey, - allowedIps: targetIps.flat() + allowedIps: targetIps.flat(), }); return response(res, { @@ -107,6 +124,11 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/target/getTarget.ts b/server/routers/target/getTarget.ts index a0a1c321..665c534b 100644 --- a/server/routers/target/getTarget.ts +++ b/server/routers/target/getTarget.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { targets } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { targets } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const getTargetSchema = z.object({ - targetId: z.string().transform(Number).pipe(z.number().int().positive()) + targetId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function getTarget(req: Request, res: Response, next: NextFunction): Promise { +export async function getTarget( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = getTargetSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -28,12 +33,21 @@ export async function getTarget(req: Request, res: Response, next: NextFunction) const { targetId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.getTarget, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.getTarget, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const target = await db.select() + const target = await db + .select() .from(targets) .where(eq(targets.targetId, targetId)) .limit(1); @@ -56,6 +70,11 @@ export async function getTarget(req: Request, res: Response, next: NextFunction) }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index a02e11e3..8adcd9ff 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -1,36 +1,43 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { targets } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { targets } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const updateTargetParamsSchema = z.object({ - targetId: z.string().transform(Number).pipe(z.number().int().positive()) + targetId: z.string().transform(Number).pipe(z.number().int().positive()), }); -const updateTargetBodySchema = z.object({ - // ip: z.string().ip().optional(), // for now we cant update the ip; you will have to delete - method: z.string().min(1).max(10).optional(), - port: z.number().int().min(1).max(65535).optional(), - protocol: z.string().optional(), - enabled: z.boolean().optional(), -}).refine(data => Object.keys(data).length > 0, { - message: "At least one field must be provided for update" -}); +const updateTargetBodySchema = z + .object({ + // ip: z.string().ip().optional(), // for now we cant update the ip; you will have to delete + method: z.string().min(1).max(10).optional(), + port: z.number().int().min(1).max(65535).optional(), + protocol: z.string().optional(), + enabled: z.boolean().optional(), + }) + .refine((data) => Object.keys(data).length > 0, { + message: "At least one field must be provided for update", + }); -export async function updateTarget(req: Request, res: Response, next: NextFunction): Promise { +export async function updateTarget( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = updateTargetParamsSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -40,7 +47,7 @@ export async function updateTarget(req: Request, res: Response, next: NextFuncti return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -49,12 +56,21 @@ export async function updateTarget(req: Request, res: Response, next: NextFuncti const updateData = parsedBody.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.updateTarget, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.updateTarget, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const updatedTarget = await db.update(targets) + const updatedTarget = await db + .update(targets) .set(updateData) .where(eq(targets.targetId, targetId)) .returning(); @@ -77,6 +93,11 @@ export async function updateTarget(req: Request, res: Response, next: NextFuncti }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/user/addUserAction.ts b/server/routers/user/addUserAction.ts index 981d651e..764f52fe 100644 --- a/server/routers/user/addUserAction.ts +++ b/server/routers/user/addUserAction.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userActions, users } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userActions, users } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { eq } from 'drizzle-orm'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { eq } from "drizzle-orm"; +import { fromError } from "zod-validation-error"; const addUserActionSchema = z.object({ userId: z.string(), @@ -15,14 +16,18 @@ const addUserActionSchema = z.object({ orgId: z.string(), }); -export async function addUserAction(req: Request, res: Response, next: NextFunction): Promise { +export async function addUserAction( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addUserActionSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -30,22 +35,42 @@ export async function addUserAction(req: Request, res: Response, next: NextFunct const { userId, actionId, orgId } = parsedBody.data; // Check if the user has permission to add user actions - const hasPermission = await checkUserActionPermission(ActionsEnum.addUserAction, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addUserAction, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Check if the user exists - const user = await db.select().from(users).where(eq(users.userId, userId)).limit(1); + const user = await db + .select() + .from(users) + .where(eq(users.userId, userId)) + .limit(1); if (user.length === 0) { - return next(createHttpError(HttpCode.NOT_FOUND, `User with ID ${userId} not found`)); + return next( + createHttpError( + HttpCode.NOT_FOUND, + `User with ID ${userId} not found` + ) + ); } - const newUserAction = await db.insert(userActions).values({ - userId, - actionId, - orgId, - }).returning(); + const newUserAction = await db + .insert(userActions) + .values({ + userId, + actionId, + orgId, + }) + .returning(); return response(res, { data: newUserAction[0], @@ -56,6 +81,11 @@ export async function addUserAction(req: Request, res: Response, next: NextFunct }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/user/addUserOrg.ts b/server/routers/user/addUserOrg.ts deleted file mode 100644 index 139fd0c4..00000000 --- a/server/routers/user/addUserOrg.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userOrgs, users, roles } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; -import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; - -const addUserParamsSchema = z.object({ - userId: z.string().uuid(), - orgId: z.string() -}); - -const addUserSchema = z.object({ - roleId: z.number().int().positive(), -}); - -export async function addUserOrg(req: Request, res: Response, next: NextFunction): Promise { - try { - const parsedParams = addUserParamsSchema.safeParse(req.params); - if (!parsedParams.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') - ) - ); - } - - const { userId, orgId } = parsedParams.data; - - const parsedBody = addUserSchema.safeParse(req.body); - if (!parsedBody.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') - ) - ); - } - - const { roleId } = parsedBody.data; - - // Check if the user has permission to add users - const hasPermission = await checkUserActionPermission(ActionsEnum.addUser, req); - if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); - } - - // Check if the user exists - const user = await db.select().from(users).where(eq(users.userId, userId)).limit(1); - if (user.length === 0) { - return next(createHttpError(HttpCode.NOT_FOUND, `User with ID ${userId} not found`)); - } - - // Check if the user is already in the organization - const existingUserOrg = await db.select() - .from(userOrgs) - .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) - .limit(1); - - if (existingUserOrg.length > 0) { - return next(createHttpError(HttpCode.CONFLICT, 'User is already a member of this organization')); - } - - // Add the user to the userOrgs table - const newUserOrg = await db.insert(userOrgs).values({ - userId, - orgId, - roleId - }).returning(); - - return response(res, { - data: newUserOrg[0], - success: true, - error: false, - message: "User added to organization successfully", - status: HttpCode.CREATED, - }); - } catch (error) { - logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); - } -} diff --git a/server/routers/user/addUserResource.ts b/server/routers/user/addUserResource.ts index 65c13000..aaa6e097 100644 --- a/server/routers/user/addUserResource.ts +++ b/server/routers/user/addUserResource.ts @@ -1,26 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userResources } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userResources } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const addUserResourceSchema = z.object({ userId: z.string(), - resourceId: z.string().transform(Number).pipe(z.number().int().positive()) + resourceId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function addUserResource(req: Request, res: Response, next: NextFunction): Promise { +export async function addUserResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addUserResourceSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -28,15 +33,26 @@ export async function addUserResource(req: Request, res: Response, next: NextFun const { userId, resourceId } = parsedBody.data; // Check if the user has permission to add user resources - const hasPermission = await checkUserActionPermission(ActionsEnum.addUserResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addUserResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const newUserResource = await db.insert(userResources).values({ - userId, - resourceId, - }).returning(); + const newUserResource = await db + .insert(userResources) + .values({ + userId, + resourceId, + }) + .returning(); return response(res, { data: newUserResource[0], @@ -47,6 +63,11 @@ export async function addUserResource(req: Request, res: Response, next: NextFun }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/user/addUserSite.ts b/server/routers/user/addUserSite.ts index 8be945a0..4691b571 100644 --- a/server/routers/user/addUserSite.ts +++ b/server/routers/user/addUserSite.ts @@ -1,27 +1,32 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources, userResources, userSites } from '@server/db/schema'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources, userResources, userSites } from "@server/db/schema"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; -import { eq } from 'drizzle-orm'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { eq } from "drizzle-orm"; +import { fromError } from "zod-validation-error"; const addUserSiteSchema = z.object({ userId: z.string(), siteId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function addUserSite(req: Request, res: Response, next: NextFunction): Promise { +export async function addUserSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addUserSiteSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -29,18 +34,30 @@ export async function addUserSite(req: Request, res: Response, next: NextFunctio const { userId, siteId } = parsedBody.data; // Check if the user has permission to add user sites - const hasPermission = await checkUserActionPermission(ActionsEnum.addUserSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addUserSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const newUserSite = await db.insert(userSites).values({ - userId, - siteId, - }).returning(); + const newUserSite = await db + .insert(userSites) + .values({ + userId, + siteId, + }) + .returning(); // Add all resources associated with the site to the user - const siteResources = await db.select() + const siteResources = await db + .select() .from(resources) .where(eq(resources.siteId, siteId)); @@ -60,6 +77,11 @@ export async function addUserSite(req: Request, res: Response, next: NextFunctio }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/user/index.ts b/server/routers/user/index.ts index 421c288a..68541b7f 100644 --- a/server/routers/user/index.ts +++ b/server/routers/user/index.ts @@ -1,6 +1,5 @@ export * from "./getUser"; export * from "./removeUserOrg"; -export * from "./addUserOrg"; export * from "./listUsers"; export * from "./setUserRole"; export * from "./inviteUser"; diff --git a/server/routers/user/removeUserAction.ts b/server/routers/user/removeUserAction.ts index 0896dd62..3a36ddec 100644 --- a/server/routers/user/removeUserAction.ts +++ b/server/routers/user/removeUserAction.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userActions } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userActions } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeUserActionParamsSchema = z.object({ userId: z.string(), @@ -15,17 +16,21 @@ const removeUserActionParamsSchema = z.object({ const removeUserActionSchema = z.object({ actionId: z.string(), - orgId: z.string() + orgId: z.string(), }); -export async function removeUserAction(req: Request, res: Response, next: NextFunction): Promise { +export async function removeUserAction( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeUserActionParamsSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -37,7 +42,7 @@ export async function removeUserAction(req: Request, res: Response, next: NextFu return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -45,17 +50,28 @@ export async function removeUserAction(req: Request, res: Response, next: NextFu const { actionId, orgId } = parsedBody.data; // Check if the user has permission to remove user actions - const hasPermission = await checkUserActionPermission(ActionsEnum.removeUserAction, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeUserAction, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const deletedUserAction = await db.delete(userActions) - .where(and( - eq(userActions.userId, userId), - eq(userActions.actionId, actionId), - eq(userActions.orgId, orgId) - )) + const deletedUserAction = await db + .delete(userActions) + .where( + and( + eq(userActions.userId, userId), + eq(userActions.actionId, actionId), + eq(userActions.orgId, orgId) + ) + ) .returning(); if (deletedUserAction.length === 0) { @@ -76,6 +92,11 @@ export async function removeUserAction(req: Request, res: Response, next: NextFu }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index 4a655961..f1f297f9 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -1,27 +1,32 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userOrgs, users } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userOrgs, users } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeUserSchema = z.object({ userId: z.string().uuid(), - orgId: z.string() + orgId: z.string(), }); -export async function removeUserOrg(req: Request, res: Response, next: NextFunction): Promise { +export async function removeUserOrg( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeUserSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -29,13 +34,22 @@ export async function removeUserOrg(req: Request, res: Response, next: NextFunct const { userId, orgId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.removeUser, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeUser, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // remove the user from the userOrgs table - await db.delete(userOrgs) + await db + .delete(userOrgs) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))); return response(res, { @@ -47,6 +61,11 @@ export async function removeUserOrg(req: Request, res: Response, next: NextFunct }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } } diff --git a/server/routers/user/removeUserResource.ts b/server/routers/user/removeUserResource.ts index 3599b37b..3eecdf3f 100644 --- a/server/routers/user/removeUserResource.ts +++ b/server/routers/user/removeUserResource.ts @@ -1,27 +1,32 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userResources } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userResources } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeUserResourceSchema = z.object({ userId: z.string(), - resourceId: z.string().transform(Number).pipe(z.number().int().positive()) + resourceId: z.string().transform(Number).pipe(z.number().int().positive()), }); -export async function removeUserResource(req: Request, res: Response, next: NextFunction): Promise { +export async function removeUserResource( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeUserResourceSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -29,13 +34,27 @@ export async function removeUserResource(req: Request, res: Response, next: Next const { userId, resourceId } = parsedParams.data; // Check if the user has permission to remove user resources - const hasPermission = await checkUserActionPermission(ActionsEnum.removeUserResource, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeUserResource, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const deletedUserResource = await db.delete(userResources) - .where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resourceId))) + const deletedUserResource = await db + .delete(userResources) + .where( + and( + eq(userResources.userId, userId), + eq(userResources.resourceId, resourceId) + ) + ) .returning(); if (deletedUserResource.length === 0) { @@ -56,6 +75,11 @@ export async function removeUserResource(req: Request, res: Response, next: Next }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/user/removeUserSite.ts b/server/routers/user/removeUserSite.ts index 362c02a8..bdd29134 100644 --- a/server/routers/user/removeUserSite.ts +++ b/server/routers/user/removeUserSite.ts @@ -1,13 +1,14 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { resources, userResources, userSites } from '@server/db/schema'; -import { and, eq } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources, userResources, userSites } from "@server/db/schema"; +import { and, eq } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const removeUserSiteParamsSchema = z.object({ userId: z.string(), @@ -17,14 +18,18 @@ const removeUserSiteSchema = z.object({ siteId: z.number().int().positive(), }); -export async function removeUserSite(req: Request, res: Response, next: NextFunction): Promise { +export async function removeUserSite( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedParams = removeUserSiteParamsSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedParams.error.errors.map(e => e.message).join(', ') + fromError(parsedParams.error).toString() ) ); } @@ -36,7 +41,7 @@ export async function removeUserSite(req: Request, res: Response, next: NextFunc return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -44,13 +49,24 @@ export async function removeUserSite(req: Request, res: Response, next: NextFunc const { siteId } = parsedBody.data; // Check if the user has permission to remove user sites - const hasPermission = await checkUserActionPermission(ActionsEnum.removeUserSite, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.removeUserSite, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } - const deletedUserSite = await db.delete(userSites) - .where(and(eq(userSites.userId, userId), eq(userSites.siteId, siteId))) + const deletedUserSite = await db + .delete(userSites) + .where( + and(eq(userSites.userId, userId), eq(userSites.siteId, siteId)) + ) .returning(); if (deletedUserSite.length === 0) { @@ -62,13 +78,20 @@ export async function removeUserSite(req: Request, res: Response, next: NextFunc ); } - const siteResources = await db.select() + const siteResources = await db + .select() .from(resources) .where(eq(resources.siteId, siteId)); for (const resource of siteResources) { - await db.delete(userResources) - .where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resource.resourceId))) + await db + .delete(userResources) + .where( + and( + eq(userResources.userId, userId), + eq(userResources.resourceId, resource.resourceId) + ) + ) .returning(); } @@ -81,6 +104,11 @@ export async function removeUserSite(req: Request, res: Response, next: NextFunc }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/server/routers/user/setUserRole.ts b/server/routers/user/setUserRole.ts index 7bd5d34c..a95f7dc0 100644 --- a/server/routers/user/setUserRole.ts +++ b/server/routers/user/setUserRole.ts @@ -1,28 +1,33 @@ -import { Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { db } from '@server/db'; -import { userOrgs, roles } from '@server/db/schema'; -import { eq, and } from 'drizzle-orm'; +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userOrgs, roles } from "@server/db/schema"; +import { eq, and } from "drizzle-orm"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; -import createHttpError from 'http-errors'; -import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; -import logger from '@server/logger'; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; const addUserRoleSchema = z.object({ userId: z.string(), roleId: z.number().int().positive(), - orgId: z.string() + orgId: z.string(), }); -export async function addUserRole(req: Request, res: Response, next: NextFunction): Promise { +export async function addUserRole( + req: Request, + res: Response, + next: NextFunction +): Promise { try { const parsedBody = addUserRoleSchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - parsedBody.error.errors.map(e => e.message).join(', ') + fromError(parsedBody.error).toString() ) ); } @@ -30,22 +35,37 @@ export async function addUserRole(req: Request, res: Response, next: NextFunctio const { userId, roleId, orgId } = parsedBody.data; // Check if the user has permission to add user roles - const hasPermission = await checkUserActionPermission(ActionsEnum.addUserRole, req); + const hasPermission = await checkUserActionPermission( + ActionsEnum.addUserRole, + req + ); if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); } // Check if the role exists and belongs to the specified org - const roleExists = await db.select() + const roleExists = await db + .select() .from(roles) .where(and(eq(roles.roleId, roleId), eq(roles.orgId, orgId))) .limit(1); if (roleExists.length === 0) { - return next(createHttpError(HttpCode.NOT_FOUND, 'Role not found or does not belong to the specified organization')); + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Role not found or does not belong to the specified organization" + ) + ); } - const newUserRole = await db.update(userOrgs) + const newUserRole = await db + .update(userOrgs) .set({ roleId }) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) .returning(); @@ -59,6 +79,11 @@ export async function addUserRole(req: Request, res: Response, next: NextFunctio }); } catch (error) { logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); } -} \ No newline at end of file +} diff --git a/src/app/[orgId]/settings/components/Header.tsx b/src/app/[orgId]/settings/components/Header.tsx index 5900a4a0..70d22c44 100644 --- a/src/app/[orgId]/settings/components/Header.tsx +++ b/src/app/[orgId]/settings/components/Header.tsx @@ -20,7 +20,7 @@ import { SelectTrigger, SelectValue, } from "@app/components/ui/select"; -import { useToast } from "@app/hooks/use-toast"; +import { useToast } from "@app/hooks/useToast"; import { ListOrgsResponse } from "@server/routers/org"; import Link from "next/link"; import { useRouter } from "next/navigation"; diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index 399c0fc2..20a084a6 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -8,6 +8,9 @@ import { internal } from "@app/api"; import { AxiosResponse } from "axios"; import { GetOrgResponse, ListOrgsResponse } from "@server/routers/org"; import { authCookieHeader } from "@app/api/cookies"; +import { cache } from "react"; + +export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: `Settings - Pangolin`, @@ -47,7 +50,8 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const { children } = props; - const user = await verifySession(); + const getUser = cache(verifySession); + const user = await getUser(); if (!user) { redirect("/auth/login"); @@ -56,20 +60,23 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const cookie = await authCookieHeader(); try { - await internal.get>( - `/org/${params.orgId}`, - cookie + const getOrg = cache(() => + internal.get>( + `/org/${params.orgId}`, + cookie + ) ); + const org = await getOrg(); } catch { redirect(`/`); } let orgs: ListOrgsResponse["orgs"] = []; try { - const res = await internal.get>( - `/orgs`, - cookie + const getOrgs = cache(() => + internal.get>(`/orgs`, cookie) ); + const res = await getOrgs(); if (res && res.data.data.orgs) { orgs = res.data.data.orgs; } diff --git a/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx b/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx index 49ba2ab8..75b6080e 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx @@ -8,7 +8,7 @@ import { import { useForm } from "react-hook-form"; import { z } from "zod"; import { cn } from "@/lib/utils"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "@/hooks/useToast"; import { Button} from "@/components/ui/button"; import { Form, diff --git a/src/app/[orgId]/settings/resources/[resourceId]/components/GeneralForm.tsx b/src/app/[orgId]/settings/resources/[resourceId]/components/GeneralForm.tsx index 777ed323..046d2cf1 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/components/GeneralForm.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/components/GeneralForm.tsx @@ -36,6 +36,8 @@ import { AxiosResponse } from "axios"; import api from "@app/api"; import { useParams } from "next/navigation"; import { useForm } from "react-hook-form"; +import { GetResourceResponse } from "@server/routers/resource"; +import { useToast } from "@app/hooks/useToast"; const GeneralFormSchema = z.object({ name: z.string(), @@ -49,6 +51,7 @@ export function GeneralForm() { const orgId = params.orgId; const { resource, updateResource } = useResourceContext(); const [sites, setSites] = useState([]); + const { toast } = useToast(); const form = useForm({ resolver: zodResolver(GeneralFormSchema), @@ -72,7 +75,24 @@ export function GeneralForm() { }, []); async function onSubmit(data: GeneralFormValues) { - await updateResource({ name: data.name, siteId: data.siteId }); + updateResource({ name: data.name, siteId: data.siteId }); + await api + .post>( + `resource/${resource?.resourceId}`, + { + name: data.name, + siteId: data.siteId, + } + ) + .catch((e) => { + toast({ + variant: "destructive", + title: "Failed to update resource", + description: + e.response?.data?.message || + "An error occurred while updating the resource", + }); + }); } return ( diff --git a/src/app/[orgId]/settings/sites/[niceId]/components/CreateSite.tsx b/src/app/[orgId]/settings/sites/[niceId]/components/CreateSite.tsx index efe4497e..e88117e4 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/components/CreateSite.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/components/CreateSite.tsx @@ -5,7 +5,7 @@ import { ChevronDownIcon } from "@radix-ui/react-icons"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { cn } from "@/lib/utils"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "@/hooks/useToast"; import { Button, buttonVariants } from "@/components/ui/button"; import { Form, @@ -78,7 +78,7 @@ export function CreateSiteForm() { setKeypair(generatedKeypair); setIsLoading(false); - api.get(`/org/${orgId}/pickSiteDefaults`) + api.get(`/org/${orgId}/pick-site-defaults`) .catch((e) => { toast({ title: "Error creating site...", diff --git a/src/app/[orgId]/settings/sites/[niceId]/components/GeneralForm.tsx b/src/app/[orgId]/settings/sites/[niceId]/components/GeneralForm.tsx index e380ffdd..1af7c03b 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/components/GeneralForm.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/components/GeneralForm.tsx @@ -15,6 +15,8 @@ import { import { Input } from "@/components/ui/input"; import { useSiteContext } from "@app/hooks/useSiteContext"; import { useForm } from "react-hook-form"; +import api from "@app/api"; +import { useToast } from "@app/hooks/useToast"; const GeneralFormSchema = z.object({ name: z.string(), @@ -24,6 +26,7 @@ type GeneralFormValues = z.infer; export function GeneralForm() { const { site, updateSite } = useSiteContext(); + const { toast } = useToast(); const form = useForm({ resolver: zodResolver(GeneralFormSchema), @@ -34,7 +37,21 @@ export function GeneralForm() { }); async function onSubmit(data: GeneralFormValues) { - await updateSite({ name: data.name }); + updateSite({ name: data.name }); + + await api + .post(`/site/${site?.siteId}`, { + name: data.name, + }) + .catch((e) => { + toast({ + variant: "destructive", + title: "Failed to update site", + description: + e.message || + "An error occurred while updating the site.", + }); + }); } return ( diff --git a/src/app/[orgId]/settings/sites/components/SitesTable.tsx b/src/app/[orgId]/settings/sites/components/SitesTable.tsx index b8da14d9..93466e3d 100644 --- a/src/app/[orgId]/settings/sites/components/SitesTable.tsx +++ b/src/app/[orgId]/settings/sites/components/SitesTable.tsx @@ -92,7 +92,7 @@ export const columns: ColumnDef[] = [ View settings diff --git a/src/app/[orgId]/settings/users/components/InviteUserForm.tsx b/src/app/[orgId]/settings/users/components/InviteUserForm.tsx index c693eb7f..2364233d 100644 --- a/src/app/[orgId]/settings/users/components/InviteUserForm.tsx +++ b/src/app/[orgId]/settings/users/components/InviteUserForm.tsx @@ -18,15 +18,30 @@ import { SelectTrigger, SelectValue, } from "@app/components/ui/select"; -import { useToast } from "@app/hooks/use-toast"; +import { useToast } from "@app/hooks/useToast"; import { zodResolver } from "@hookform/resolvers/zod"; import { InviteUserBody, InviteUserResponse } from "@server/routers/user"; import { AxiosResponse } from "axios"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { useParams } from "next/navigation"; import CopyTextBox from "@app/components/CopyTextBox"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle, +} from "@app/components/Credenza"; +import { useOrgContext } from "@app/hooks/useOrgContext"; + +type InviteUserFormProps = { + open: boolean; + setOpen: (open: boolean) => void; +}; const formSchema = z.object({ email: z.string().email({ message: "Invalid email address" }), @@ -34,9 +49,9 @@ const formSchema = z.object({ roleId: z.string(), }); -export default function InviteUserForm() { +export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) { const { toast } = useToast(); - const { orgId } = useParams(); + const { org } = useOrgContext(); const [inviteLink, setInviteLink] = useState(null); const [loading, setLoading] = useState(false); @@ -74,7 +89,7 @@ export default function InviteUserForm() { const res = await api .post>( - `/org/${orgId}/create-invite`, + `/org/${org?.org.orgId}/create-invite`, { email: values.email, roleId: parseInt(values.roleId), @@ -107,117 +122,151 @@ export default function InviteUserForm() { return ( <> - {!inviteLink && ( -
- - ( - - Email - - - - - - )} - /> - ( - - Role - + + + + )} + /> + ( + + Role + - - - )} - /> - ( - - Valid For - + + + )} + /> + ( + + Valid For + - - - )} - /> + + + + + + + {validFor.map( + (option) => ( + + { + option.name + } + + ) + )} + + + + + )} + /> + + + )} + + {inviteLink && ( +
+

+ The user has been successfully invited. They + must access the link below to accept the + invitation. +

+

+ The invite will expire in{" "} + + {expiresInDays}{" "} + {expiresInDays === 1 ? "day" : "days"} + + . +

+ +
+ )} + + - - - )} - - {inviteLink && ( -
-

- The user has been successfully invited. They must access - the link below to accept the invitation. -

-

- The invite will expire in{" "} - - {expiresInDays}{" "} - {expiresInDays === 1 ? "day" : "days"} - - . -

- -
- )} + + + +
+ + ); } diff --git a/src/app/[orgId]/settings/users/components/UsersTable.tsx b/src/app/[orgId]/settings/users/components/UsersTable.tsx index d8f730d7..54560985 100644 --- a/src/app/[orgId]/settings/users/components/UsersTable.tsx +++ b/src/app/[orgId]/settings/users/components/UsersTable.tsx @@ -10,16 +10,8 @@ import { import { Button } from "@app/components/ui/button"; import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import { UsersDataTable } from "./UsersDataTable"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@app/components/ui/dialog"; import { useState } from "react"; import InviteUserForm from "./InviteUserForm"; -import { Credenza, CredenzaTitle, CredenzaDescription, CredenzaHeader, CredenzaClose, CredenzaFooter, CredenzaContent, CredenzaBody } from "@app/components/Credenza"; export type UserRow = { id: string; @@ -74,19 +66,10 @@ export default function UsersTable({ users }: UsersTableProps) { return ( <> - - - - Invite User - - Give new users access to your organization - - - - - - - + ; @@ -10,15 +13,36 @@ type UsersPageProps = { export default async function UsersPage(props: UsersPageProps) { const params = await props.params; + let users: ListUsersResponse["users"] = []; - try { - const res = await internal.get>( + const res = await internal + .get>( `/org/${params.orgId}/users`, await authCookieHeader() - ); + ) + .catch((e) => { + console.error(e); + }); + + if (res && res.status === 200) { users = res.data.data.users; - } catch (e) { - console.error("Error fetching users", e); + } + + let org: GetOrgResponse | null = null; + const getOrg = cache(async () => + internal + .get>( + `/org/${params.orgId}`, + await authCookieHeader() + ) + .catch((e) => { + console.error(e); + }) + ); + const orgRes = await getOrg(); + + if (orgRes && orgRes.status === 200) { + org = orgRes.data.data; } const userRows: UserRow[] = users.map((user) => { @@ -40,7 +64,9 @@ export default async function UsersPage(props: UsersPageProps) {

- + + + ); } diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 88d9fcad..e14fff25 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -2,12 +2,16 @@ import LoginForm from "@app/app/auth/login/LoginForm"; import { verifySession } from "@app/lib/auth/verifySession"; import Link from "next/link"; import { redirect } from "next/navigation"; +import { cache } from "react"; + +export const dynamic = 'force-dynamic'; export default async function Page(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const searchParams = await props.searchParams; - const user = await verifySession(); + const getUser = cache(verifySession); + const user = await getUser(); if (user) { redirect("/"); diff --git a/src/app/auth/signup/page.tsx b/src/app/auth/signup/page.tsx index c5088423..785a168c 100644 --- a/src/app/auth/signup/page.tsx +++ b/src/app/auth/signup/page.tsx @@ -2,12 +2,16 @@ import SignupForm from "@app/app/auth/signup/SignupForm"; import { verifySession } from "@app/lib/auth/verifySession"; import Link from "next/link"; import { redirect } from "next/navigation"; +import { cache } from "react"; + +export const dynamic = 'force-dynamic'; export default async function Page(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const searchParams = await props.searchParams; - const user = await verifySession(); + const getUser = cache(verifySession); + const user = await getUser(); if (user) { redirect("/"); diff --git a/src/app/auth/verify-email/VerifyEmailForm.tsx b/src/app/auth/verify-email/VerifyEmailForm.tsx index becf4dc9..b1071462 100644 --- a/src/app/auth/verify-email/VerifyEmailForm.tsx +++ b/src/app/auth/verify-email/VerifyEmailForm.tsx @@ -32,7 +32,7 @@ import { AxiosResponse } from "axios"; import { VerifyEmailResponse } from "@server/routers/auth"; import { Loader2 } from "lucide-react"; import { Alert, AlertDescription } from "../../../components/ui/alert"; -import { useToast } from "@app/hooks/use-toast"; +import { useToast } from "@app/hooks/useToast"; import { useRouter } from "next/navigation"; const FormSchema = z.object({ diff --git a/src/app/auth/verify-email/page.tsx b/src/app/auth/verify-email/page.tsx index 2444ea13..4a9560aa 100644 --- a/src/app/auth/verify-email/page.tsx +++ b/src/app/auth/verify-email/page.tsx @@ -1,6 +1,9 @@ import VerifyEmailForm from "@app/app/auth/verify-email/VerifyEmailForm"; import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; +import { cache } from "react"; + +export const dynamic = "force-dynamic"; export default async function Page(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; @@ -10,7 +13,8 @@ export default async function Page(props: { } const searchParams = await props.searchParams; - const user = await verifySession(); + const getUser = cache(verifySession); + const user = await getUser(); if (!user) { redirect("/"); diff --git a/src/app/page.tsx b/src/app/page.tsx index b1197cce..3ace08a6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,12 +7,16 @@ import { AxiosResponse } from "axios"; import { ArrowUpRight } from "lucide-react"; import Link from "next/link"; import { redirect } from "next/navigation"; +import { cache } from "react"; + +export const dynamic = "force-dynamic"; export default async function Page(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const params = await props.searchParams; // this is needed to prevent static optimization - const user = await verifySession(); + const getUser = cache(verifySession); + const user = await getUser(); if (!user) { redirect("/auth/login"); diff --git a/src/app/profile/account/account-form.tsx b/src/app/profile/account/account-form.tsx index 2cb9bf1e..a3f48044 100644 --- a/src/app/profile/account/account-form.tsx +++ b/src/app/profile/account/account-form.tsx @@ -6,7 +6,7 @@ import { useForm } from "react-hook-form" import { z } from "zod" import { cn } from "@/lib/utils" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { Command, diff --git a/src/app/profile/appearance/appearance-form.tsx b/src/app/profile/appearance/appearance-form.tsx index 9a81f37a..3d085541 100644 --- a/src/app/profile/appearance/appearance-form.tsx +++ b/src/app/profile/appearance/appearance-form.tsx @@ -6,7 +6,7 @@ import { useForm } from "react-hook-form" import { z } from "zod" import { cn } from "@/lib/utils" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button, buttonVariants } from "@/components/ui/button" import { Form, diff --git a/src/app/profile/display/display-form.tsx b/src/app/profile/display/display-form.tsx index cf1c6c0d..950ecd5d 100644 --- a/src/app/profile/display/display-form.tsx +++ b/src/app/profile/display/display-form.tsx @@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { diff --git a/src/app/profile/notifications/notifications-form.tsx b/src/app/profile/notifications/notifications-form.tsx index cc191252..0eb215b5 100644 --- a/src/app/profile/notifications/notifications-form.tsx +++ b/src/app/profile/notifications/notifications-form.tsx @@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { diff --git a/src/app/profile/profile-form.tsx b/src/app/profile/profile-form.tsx index 0911ac0f..70ec6d6f 100644 --- a/src/app/profile/profile-form.tsx +++ b/src/app/profile/profile-form.tsx @@ -6,7 +6,7 @@ import { useFieldArray, useForm } from "react-hook-form" import { z } from "zod" import { cn } from "@/lib/utils" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index 62093157..483303e4 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import Link from "next/link"; import api from "@app/api"; -import { toast } from "@app/hooks/use-toast"; +import { toast } from "@app/hooks/useToast"; import { useCallback, useEffect, useState } from "react"; import { Card, diff --git a/src/components/account-form.tsx b/src/components/account-form.tsx index 2cb9bf1e..a3f48044 100644 --- a/src/components/account-form.tsx +++ b/src/components/account-form.tsx @@ -6,7 +6,7 @@ import { useForm } from "react-hook-form" import { z } from "zod" import { cn } from "@/lib/utils" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { Command, diff --git a/src/components/appearance-form.tsx b/src/components/appearance-form.tsx index 95ac2d2d..2d97cf21 100644 --- a/src/components/appearance-form.tsx +++ b/src/components/appearance-form.tsx @@ -1,169 +1,179 @@ -"use client" +"use client"; -import { zodResolver } from "@hookform/resolvers/zod" -import { ChevronDownIcon } from "@radix-ui/react-icons" -import { useForm } from "react-hook-form" -import { z } from "zod" +import { zodResolver } from "@hookform/resolvers/zod"; +import { ChevronDownIcon } from "@radix-ui/react-icons"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; -import { cn } from "@/lib/utils" -import { toast } from "@/hooks/use-toast" -import { Button, buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils"; +import { toast } from "@/hooks/useToast"; +import { Button, buttonVariants } from "@/components/ui/button"; import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" -import { useSiteContext } from "@app/hooks/useSiteContext" + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { useSiteContext } from "@app/hooks/useSiteContext"; const appearanceFormSchema = z.object({ - theme: z.enum(["light", "dark"], { - required_error: "Please select a theme.", - }), - font: z.enum(["inter", "manrope", "system"], { - invalid_type_error: "Select a font", - required_error: "Please select a font.", - }), -}) + theme: z.enum(["light", "dark"], { + required_error: "Please select a theme.", + }), + font: z.enum(["inter", "manrope", "system"], { + invalid_type_error: "Select a font", + required_error: "Please select a font.", + }), +}); -type AppearanceFormValues = z.infer +type AppearanceFormValues = z.infer; // This can come from your database or API. const defaultValues: Partial = { - theme: "light", -} + theme: "light", +}; export function AppearanceForm() { const site = useSiteContext(); console.log(site); - - const form = useForm({ - resolver: zodResolver(appearanceFormSchema), - defaultValues, - }) - function onSubmit(data: AppearanceFormValues) { - toast({ - title: "You submitted the following values:", - description: ( -
-          {JSON.stringify(data, null, 2)}
-        
- ), - }) - } + const form = useForm({ + resolver: zodResolver(appearanceFormSchema), + defaultValues, + }); - return ( -
- - ( - - Font -
- - + + + + + + +
+ + Set the font you want to use in the dashboard. + + +
)} - {...field} - > - - - - - - - - - Set the font you want to use in the dashboard. - - - - )} - /> - ( - - Theme - - Select the theme for the dashboard. - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Light - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Dark - - - - - - )} - /> + /> + ( + + Theme + + Select the theme for the dashboard. + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Light + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Dark + + + + + + )} + /> - - - - ) + + + + ); } diff --git a/src/components/display-form.tsx b/src/components/display-form.tsx index cf1c6c0d..950ecd5d 100644 --- a/src/components/display-form.tsx +++ b/src/components/display-form.tsx @@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { diff --git a/src/components/notifications-form.tsx b/src/components/notifications-form.tsx index cc191252..0eb215b5 100644 --- a/src/components/notifications-form.tsx +++ b/src/components/notifications-form.tsx @@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { diff --git a/src/components/profile-form.tsx b/src/components/profile-form.tsx index 0911ac0f..70ec6d6f 100644 --- a/src/components/profile-form.tsx +++ b/src/components/profile-form.tsx @@ -6,7 +6,7 @@ import { useFieldArray, useForm } from "react-hook-form" import { z } from "zod" import { cn } from "@/lib/utils" -import { toast } from "@/hooks/use-toast" +import { toast } from "@/hooks/useToast" import { Button } from "@/components/ui/button" import { diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index 171beb46..d1bd3c3d 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -1,6 +1,6 @@ "use client" -import { useToast } from "@/hooks/use-toast" +import { useToast } from "@/hooks/useToast" import { Toast, ToastClose, diff --git a/src/contexts/orgContext.ts b/src/contexts/orgContext.ts new file mode 100644 index 00000000..d0ce10f5 --- /dev/null +++ b/src/contexts/orgContext.ts @@ -0,0 +1,11 @@ +import { GetOrgResponse } from "@server/routers/org"; +import { createContext } from "react"; + +interface OrgContextType { + org: GetOrgResponse | null; + updateOrg: (updateOrg: Partial) => void; +} + +const OrgContext = createContext(undefined); + +export default OrgContext; diff --git a/src/contexts/resourceContext.ts b/src/contexts/resourceContext.ts index 70b87b9b..e3090b3d 100644 --- a/src/contexts/resourceContext.ts +++ b/src/contexts/resourceContext.ts @@ -3,9 +3,11 @@ import { createContext } from "react"; interface ResourceContextType { resource: GetResourceResponse | null; - updateResource: (updatedResource: Partial) => Promise; + updateResource: (updatedResource: Partial) => void; } -const ResourceContext = createContext(undefined); +const ResourceContext = createContext( + undefined +); -export default ResourceContext; \ No newline at end of file +export default ResourceContext; diff --git a/src/contexts/siteContext.ts b/src/contexts/siteContext.ts index 0e35b8bf..cb42cbd2 100644 --- a/src/contexts/siteContext.ts +++ b/src/contexts/siteContext.ts @@ -3,9 +3,9 @@ import { createContext } from "react"; interface SiteContextType { site: GetSiteResponse | null; - updateSite: (updatedSite: Partial) => Promise; + updateSite: (updatedSite: Partial) => void; } const SiteContext = createContext(undefined); -export default SiteContext; \ No newline at end of file +export default SiteContext; diff --git a/src/hooks/useOrgContext.ts b/src/hooks/useOrgContext.ts new file mode 100644 index 00000000..ced28a6e --- /dev/null +++ b/src/hooks/useOrgContext.ts @@ -0,0 +1,10 @@ +import OrgContext from "@app/contexts/orgContext"; +import { useContext } from "react"; + +export function useOrgContext() { + const context = useContext(OrgContext); + if (context === undefined) { + throw new Error("useOrgContext must be used within a OrgProvider"); + } + return context; +} diff --git a/src/hooks/useResourceContext.ts b/src/hooks/useResourceContext.ts index 81abeae3..eb5b1f34 100644 --- a/src/hooks/useResourceContext.ts +++ b/src/hooks/useResourceContext.ts @@ -4,7 +4,9 @@ import { useContext } from "react"; export function useResourceContext() { const context = useContext(ResourceContext); if (context === undefined) { - throw new Error('useResourceContext must be used within a ResourceProvider'); + throw new Error( + "useResourceContext must be used within a ResourceProvider" + ); } return context; -} \ No newline at end of file +} diff --git a/src/hooks/use-toast.ts b/src/hooks/useToast.ts similarity index 100% rename from src/hooks/use-toast.ts rename to src/hooks/useToast.ts diff --git a/src/lib/auth/verifySession.ts b/src/lib/auth/verifySession.ts index 5b488fa3..59f9386c 100644 --- a/src/lib/auth/verifySession.ts +++ b/src/lib/auth/verifySession.ts @@ -14,4 +14,4 @@ export async function verifySession(): Promise { } catch { return null; } -} +} \ No newline at end of file diff --git a/src/providers/OrgProvider.tsx b/src/providers/OrgProvider.tsx new file mode 100644 index 00000000..29008bf8 --- /dev/null +++ b/src/providers/OrgProvider.tsx @@ -0,0 +1,39 @@ +"use client"; + +import OrgContext from "@app/contexts/orgContext"; +import { GetOrgResponse } from "@server/routers/org"; +import { useState } from "react"; + +interface OrgProviderProps { + children: React.ReactNode; + org: GetOrgResponse | null; +} + +export function OrgProvider({ children, org: serverOrg }: OrgProviderProps) { + const [org, setOrg] = useState(serverOrg); + + const updateOrg = (updatedOrg: Partial) => { + if (!org) { + throw new Error("No org to update"); + } + + setOrg((prev) => { + if (!prev) { + return prev; + } + + return { + ...prev, + ...updatedOrg, + }; + }); + }; + + return ( + + {children} + + ); +} + +export default OrgProvider; diff --git a/src/providers/ResourceProvider.tsx b/src/providers/ResourceProvider.tsx index 015a0f6f..28748697 100644 --- a/src/providers/ResourceProvider.tsx +++ b/src/providers/ResourceProvider.tsx @@ -1,10 +1,7 @@ "use client"; -import api from "@app/api"; import ResourceContext from "@app/contexts/resourceContext"; -import { toast } from "@app/hooks/use-toast"; import { GetResourceResponse } from "@server/routers/resource/getResource"; -import { AxiosResponse } from "axios"; import { useState } from "react"; interface ResourceProviderProps { @@ -12,34 +9,36 @@ interface ResourceProviderProps { resource: GetResourceResponse | null; } -export function ResourceProvider({ children, resource: serverResource }: ResourceProviderProps) { - const [resource, setResource] = useState(serverResource); +export function ResourceProvider({ + children, + resource: serverResource, +}: ResourceProviderProps) { + const [resource, setResource] = useState( + serverResource + ); - const updateResource = async (updatedResource: Partial) => { - try { - if (!resource) { - throw new Error("No resource to update"); + const updateResource = (updatedResource: Partial) => { + if (!resource) { + throw new Error("No resource to update"); + } + + setResource((prev) => { + if (!prev) { + return prev; } - const res = await api.post>( - `resource/${resource.resourceId}`, - updatedResource, - ); - setResource(res.data.data); - toast({ - title: "Resource updated!", - }); - } catch (error) { - console.error(error); - toast({ - variant: "destructive", - title: "Error updating resource...", - }) - } + return { + ...prev, + ...updatedResource, + }; + }); }; - - return {children}; + return ( + + {children} + + ); } -export default ResourceProvider; \ No newline at end of file +export default ResourceProvider; diff --git a/src/providers/SiteProvider.tsx b/src/providers/SiteProvider.tsx index fbc4f2e5..6129e092 100644 --- a/src/providers/SiteProvider.tsx +++ b/src/providers/SiteProvider.tsx @@ -1,10 +1,7 @@ "use client"; -import api from "@app/api"; import SiteContext from "@app/contexts/siteContext"; -import { toast } from "@app/hooks/use-toast"; import { GetSiteResponse } from "@server/routers/site/getSite"; -import { AxiosResponse } from "axios"; import { useState } from "react"; interface SiteProviderProps { @@ -12,34 +9,32 @@ interface SiteProviderProps { site: GetSiteResponse | null; } -export function SiteProvider({ children, site: serverSite }: SiteProviderProps) { +export function SiteProvider({ + children, + site: serverSite, +}: SiteProviderProps) { const [site, setSite] = useState(serverSite); - const updateSite = async (updatedSite: Partial) => { - try { - if (!site) { - throw new Error("No site to update"); - } - - const res = await api.post>( - `site/${site.siteId}`, - updatedSite, - ); - setSite(res.data.data); - toast({ - title: "Site updated!", - }); - } catch (error) { - console.error(error); - toast({ - variant: "destructive", - title: "Error updating site...", - }) + const updateSite = (updatedSite: Partial) => { + if (!site) { + throw new Error("No site to update"); } + setSite((prev) => { + if (!prev) { + return prev; + } + return { + ...prev, + ...updatedSite, + }; + }); }; - - return {children}; + return ( + + {children} + + ); } -export default SiteProvider; \ No newline at end of file +export default SiteProvider;