From 31e25be92d1d89b6a720242413884b728c990fcd Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Thu, 11 Jul 2024 18:38:16 +0000 Subject: [PATCH 1/8] disabled duplicateRoutes --- src/server/api/routers/networkRouter.ts | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index b4fb6b0e..594c1545 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -10,7 +10,7 @@ import { type TagsByName, type NetworkEntity, RoutesEntity } from "~/types/local import { MemberEntity, type CapabilitiesByName } from "~/types/local/member"; import { type CentralNetwork } from "~/types/central/network"; import { checkUserOrganizationRole } from "~/utils/role"; -import { Prisma, Role } from "@prisma/client"; +import { Role } from "@prisma/client"; import { HookType, NetworkConfigChanged, NetworkDeleted } from "~/types/webhooks"; import { sendWebhook } from "~/utils/webhook"; import { fetchZombieMembers, syncMemberPeersAndStatus } from "../services/memberService"; @@ -193,23 +193,23 @@ export const networkRouter = createTRPCRouter({ } // check if there are any other networks with the same routes. - let duplicateRoutes: DuplicateRoutes[] = []; - if (targetIPs.length > 0) { - duplicateRoutes = await ctx.prisma.$queryRaw` - SELECT "authorId", "routes", "name", "nwid" - FROM "network" - WHERE "authorId" = ${ctx.session.user.id} - AND EXISTS ( - SELECT 1 - FROM jsonb_array_elements("routes") as route - WHERE route->>'target' IN (${Prisma.join(targetIPs)}) - ) - AND "nwid" != ${input.nwid}; - `; - } else { - // Handle the case when targetIPs is empty - duplicateRoutes = []; - } + const duplicateRoutes: DuplicateRoutes[] = []; + // if (targetIPs.length > 0) { + // duplicateRoutes = await ctx.prisma.$queryRaw` + // SELECT "authorId", "routes", "name", "nwid" + // FROM "network" + // WHERE "authorId" = ${ctx.session.user.id} + // AND EXISTS ( + // SELECT 1 + // FROM jsonb_array_elements("routes") as route + // WHERE route->>'target' IN (${Prisma.join(targetIPs)}) + // ) + // AND "nwid" != ${input.nwid}; + // `; + // } else { + // // Handle the case when targetIPs is empty + // duplicateRoutes = []; + // } // Extract duplicated IPs const duplicatedIPs = duplicateRoutes.flatMap((network) => From 489b8e117a778789ece31c8778f74bbfd848966a Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Thu, 11 Jul 2024 18:39:53 +0000 Subject: [PATCH 2/8] display load time --- src/server/api/routers/networkRouter.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 594c1545..9e4af17e 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -75,6 +75,7 @@ export const networkRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { + console.time("Total time for loading Networks Details"); if (input.central) { return await ztController.central_network_detail(ctx, input.nwid, input.central); } @@ -223,6 +224,8 @@ export const networkRouter = createTRPCRouter({ // Convert the map back to an array of merged members const mergedMembers = [...mergedMembersMap.values()]; + + console.timeEnd("Total time for loading Networks Details"); // Construct the final response object return { network: { From 79a631793958c000bdf7dfc8be69710f877b58eb Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Thu, 11 Jul 2024 20:17:12 +0000 Subject: [PATCH 3/8] time functions --- src/server/api/routers/networkRouter.ts | 59 ++++++++++++++----------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 9e4af17e..677d26b2 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -10,7 +10,7 @@ import { type TagsByName, type NetworkEntity, RoutesEntity } from "~/types/local import { MemberEntity, type CapabilitiesByName } from "~/types/local/member"; import { type CentralNetwork } from "~/types/central/network"; import { checkUserOrganizationRole } from "~/utils/role"; -import { Role } from "@prisma/client"; +import { Prisma, Role } from "@prisma/client"; import { HookType, NetworkConfigChanged, NetworkDeleted } from "~/types/webhooks"; import { sendWebhook } from "~/utils/webhook"; import { fetchZombieMembers, syncMemberPeersAndStatus } from "../services/memberService"; @@ -79,7 +79,7 @@ export const networkRouter = createTRPCRouter({ if (input.central) { return await ztController.central_network_detail(ctx, input.nwid, input.central); } - + console.time("Loading Network from Database"); // First, retrieve the network with organization details let networkFromDatabase = await ctx.prisma.network.findUnique({ where: { @@ -93,8 +93,9 @@ export const networkRouter = createTRPCRouter({ if (!networkFromDatabase) { return throwError("Network not found!"); } + console.timeEnd("Loading Network from Database"); - // Check if the user is the author of the network or part of the associated organization + console.time("Fetch Organization Details"); // Check if the user is the author of the network or part of the associated organization const isAuthor = networkFromDatabase.authorId === ctx.session.user.id; const isMemberOfOrganization = networkFromDatabase.organizationId && @@ -110,7 +111,9 @@ export const networkRouter = createTRPCRouter({ if (!isAuthor && !isMemberOfOrganization) { return throwError("You do not have access to this network!"); } + console.timeEnd("Fetch Organization Details"); + console.time("Loading Network Details from Controller"); /** * Response from the ztController.local_network_detail method. * @type {Promise} @@ -120,9 +123,9 @@ export const networkRouter = createTRPCRouter({ .catch((err: APIError) => { throwError(`${err.message}`); }); - + console.timeEnd("Loading Network Details from Controller"); if (!ztControllerResponse) return throwError("Failed to get network details!"); - + console.time("Syncing Member Peers and Status"); /** * Syncs member peers and status. */ @@ -131,7 +134,9 @@ export const networkRouter = createTRPCRouter({ input.nwid, ztControllerResponse.members, ); + console.timeEnd("Syncing Member Peers and Status"); + console.time("Fetch Zombie Members"); /** * Fetches zombie members. */ @@ -139,6 +144,7 @@ export const networkRouter = createTRPCRouter({ input.nwid, ztControllerResponse.members, ); + console.timeEnd("Fetch Zombie Members"); // Generate CIDR options for IP configuration const { cidrOptions } = IPv4gen(null, []); @@ -152,6 +158,7 @@ export const networkRouter = createTRPCRouter({ mergedMembersMap.set(member.id, member); } + console.time("Fetch Members from Database"); // Fetch members from the database for a given network ID where the members are not deleted const databaseMembers = await ctx.prisma.network_members.findMany({ where: { @@ -159,14 +166,14 @@ export const networkRouter = createTRPCRouter({ deleted: false, }, }); - + console.timeEnd("Fetch Members from Database"); // Process databaseMembers for (const member of databaseMembers) { if (!mergedMembersMap.has(member.id)) { mergedMembersMap.set(member.id, member); } } - + console.time("Update Network Routes in Database"); // if the networkFromDatabase.routes is not equal to the ztControllerResponse.routes, update the networkFromDatabase.routes if ( JSON.stringify(networkFromDatabase.routes) !== @@ -186,31 +193,32 @@ export const networkRouter = createTRPCRouter({ } // check if there is other network using same routes and return a notification const targetIPs = ztControllerResponse.network.routes.map((route) => route.target); - + console.timeEnd("Update Network Routes in Database"); interface DuplicateRoutes { authorId: string; routes: RoutesEntity[]; name: string; } + console.time("Check for Duplicate Routes"); // check if there are any other networks with the same routes. - const duplicateRoutes: DuplicateRoutes[] = []; - // if (targetIPs.length > 0) { - // duplicateRoutes = await ctx.prisma.$queryRaw` - // SELECT "authorId", "routes", "name", "nwid" - // FROM "network" - // WHERE "authorId" = ${ctx.session.user.id} - // AND EXISTS ( - // SELECT 1 - // FROM jsonb_array_elements("routes") as route - // WHERE route->>'target' IN (${Prisma.join(targetIPs)}) - // ) - // AND "nwid" != ${input.nwid}; - // `; - // } else { - // // Handle the case when targetIPs is empty - // duplicateRoutes = []; - // } + let duplicateRoutes: DuplicateRoutes[] = []; + if (targetIPs.length > 0) { + duplicateRoutes = await ctx.prisma.$queryRaw` + SELECT "authorId", "routes", "name", "nwid" + FROM "network" + WHERE "authorId" = ${ctx.session.user.id} + AND EXISTS ( + SELECT 1 + FROM jsonb_array_elements("routes") as route + WHERE route->>'target' IN (${Prisma.join(targetIPs)}) + ) + AND "nwid" != ${input.nwid}; + `; + } else { + // Handle the case when targetIPs is empty + duplicateRoutes = []; + } // Extract duplicated IPs const duplicatedIPs = duplicateRoutes.flatMap((network) => @@ -218,6 +226,7 @@ export const networkRouter = createTRPCRouter({ .filter((route) => targetIPs.includes(route.target)) .map((route) => route.target), ); + console.timeEnd("Check for Duplicate Routes"); // Remove duplicates from the list of duplicated IPs const uniqueDuplicatedIPs = [...new Set(duplicatedIPs)]; From d4e9021c7273f6b42d518fa3c22dd32cd01c36b0 Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Thu, 11 Jul 2024 20:20:30 +0000 Subject: [PATCH 4/8] error handler --- src/server/api/routers/networkRouter.ts | 299 ++++++++++++------------ 1 file changed, 155 insertions(+), 144 deletions(-) diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 677d26b2..639e74ff 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -76,135 +76,142 @@ export const networkRouter = createTRPCRouter({ ) .query(async ({ ctx, input }) => { console.time("Total time for loading Networks Details"); - if (input.central) { - return await ztController.central_network_detail(ctx, input.nwid, input.central); - } - console.time("Loading Network from Database"); - // First, retrieve the network with organization details - let networkFromDatabase = await ctx.prisma.network.findUnique({ - where: { - nwid: input.nwid, - }, - include: { - organization: true, - }, - }); - - if (!networkFromDatabase) { - return throwError("Network not found!"); - } - console.timeEnd("Loading Network from Database"); - - console.time("Fetch Organization Details"); // Check if the user is the author of the network or part of the associated organization - const isAuthor = networkFromDatabase.authorId === ctx.session.user.id; - const isMemberOfOrganization = - networkFromDatabase.organizationId && - (await ctx.prisma.organization.findFirst({ + try { + if (input.central) { + return await ztController.central_network_detail( + ctx, + input.nwid, + input.central, + ); + } + console.time("Loading Network from Database"); + // First, retrieve the network with organization details + let networkFromDatabase = await ctx.prisma.network.findUnique({ where: { - id: networkFromDatabase.organizationId, - users: { - some: { id: ctx.session.user.id }, - }, + nwid: input.nwid, }, - })); + include: { + organization: true, + }, + }); - if (!isAuthor && !isMemberOfOrganization) { - return throwError("You do not have access to this network!"); - } - console.timeEnd("Fetch Organization Details"); + if (!networkFromDatabase) { + return throwError("Network not found!"); + } + console.timeEnd("Loading Network from Database"); - console.time("Loading Network Details from Controller"); - /** - * Response from the ztController.local_network_detail method. - * @type {Promise} - */ - const ztControllerResponse = await ztController - .local_network_detail(ctx, networkFromDatabase.nwid, false) - .catch((err: APIError) => { - throwError(`${err.message}`); - }); - console.timeEnd("Loading Network Details from Controller"); - if (!ztControllerResponse) return throwError("Failed to get network details!"); - console.time("Syncing Member Peers and Status"); - /** - * Syncs member peers and status. - */ - const membersWithStatusAndPeers = await syncMemberPeersAndStatus( - ctx, - input.nwid, - ztControllerResponse.members, - ); - console.timeEnd("Syncing Member Peers and Status"); + console.time("Fetch Organization Details"); // Check if the user is the author of the network or part of the associated organization + const isAuthor = networkFromDatabase.authorId === ctx.session.user.id; + const isMemberOfOrganization = + networkFromDatabase.organizationId && + (await ctx.prisma.organization.findFirst({ + where: { + id: networkFromDatabase.organizationId, + users: { + some: { id: ctx.session.user.id }, + }, + }, + })); - console.time("Fetch Zombie Members"); - /** - * Fetches zombie members. - */ - const zombieMembers = await fetchZombieMembers( - input.nwid, - ztControllerResponse.members, - ); - console.timeEnd("Fetch Zombie Members"); + if (!isAuthor && !isMemberOfOrganization) { + return throwError("You do not have access to this network!"); + } + console.timeEnd("Fetch Organization Details"); + + console.time("Loading Network Details from Controller"); + /** + * Response from the ztController.local_network_detail method. + * @type {Promise} + */ + const ztControllerResponse = await ztController + .local_network_detail(ctx, networkFromDatabase.nwid, false) + .catch((err: APIError) => { + throwError(`${err.message}`); + }); + console.timeEnd("Loading Network Details from Controller"); + if (!ztControllerResponse) return throwError("Failed to get network details!"); + console.time("Syncing Member Peers and Status"); + /** + * Syncs member peers and status. + */ + const membersWithStatusAndPeers = await syncMemberPeersAndStatus( + ctx, + input.nwid, + ztControllerResponse.members, + ); + console.timeEnd("Syncing Member Peers and Status"); - // Generate CIDR options for IP configuration - const { cidrOptions } = IPv4gen(null, []); + console.time("Fetch Zombie Members"); + /** + * Fetches zombie members. + */ + const zombieMembers = await fetchZombieMembers( + input.nwid, + ztControllerResponse.members, + ); + console.timeEnd("Fetch Zombie Members"); - /** - * Merging logic to ensure that members who only exist in local database ( added manually ) are also included in the response - * Create a map to store members by their id for efficient lookup - */ - const mergedMembersMap = new Map(); - for (const member of membersWithStatusAndPeers) { - mergedMembersMap.set(member.id, member); - } + // Generate CIDR options for IP configuration + const { cidrOptions } = IPv4gen(null, []); - console.time("Fetch Members from Database"); - // Fetch members from the database for a given network ID where the members are not deleted - const databaseMembers = await ctx.prisma.network_members.findMany({ - where: { - nwid: input.nwid, - deleted: false, - }, - }); - console.timeEnd("Fetch Members from Database"); - // Process databaseMembers - for (const member of databaseMembers) { - if (!mergedMembersMap.has(member.id)) { + /** + * Merging logic to ensure that members who only exist in local database ( added manually ) are also included in the response + * Create a map to store members by their id for efficient lookup + */ + const mergedMembersMap = new Map(); + for (const member of membersWithStatusAndPeers) { mergedMembersMap.set(member.id, member); } - } - console.time("Update Network Routes in Database"); - // if the networkFromDatabase.routes is not equal to the ztControllerResponse.routes, update the networkFromDatabase.routes - if ( - JSON.stringify(networkFromDatabase.routes) !== - JSON.stringify(ztControllerResponse.network.routes) - ) { - networkFromDatabase = await ctx.prisma.network.update({ + + console.time("Fetch Members from Database"); + // Fetch members from the database for a given network ID where the members are not deleted + const databaseMembers = await ctx.prisma.network_members.findMany({ where: { nwid: input.nwid, - }, - data: { - routes: ztControllerResponse.network.routes as string[], - }, - include: { - organization: true, + deleted: false, }, }); - } - // check if there is other network using same routes and return a notification - const targetIPs = ztControllerResponse.network.routes.map((route) => route.target); - console.timeEnd("Update Network Routes in Database"); - interface DuplicateRoutes { - authorId: string; - routes: RoutesEntity[]; - name: string; - } + console.timeEnd("Fetch Members from Database"); + // Process databaseMembers + for (const member of databaseMembers) { + if (!mergedMembersMap.has(member.id)) { + mergedMembersMap.set(member.id, member); + } + } + console.time("Update Network Routes in Database"); + // if the networkFromDatabase.routes is not equal to the ztControllerResponse.routes, update the networkFromDatabase.routes + if ( + JSON.stringify(networkFromDatabase.routes) !== + JSON.stringify(ztControllerResponse.network.routes) + ) { + networkFromDatabase = await ctx.prisma.network.update({ + where: { + nwid: input.nwid, + }, + data: { + routes: ztControllerResponse.network.routes as string[], + }, + include: { + organization: true, + }, + }); + } + // check if there is other network using same routes and return a notification + const targetIPs = ztControllerResponse.network.routes.map( + (route) => route.target, + ); + console.timeEnd("Update Network Routes in Database"); + interface DuplicateRoutes { + authorId: string; + routes: RoutesEntity[]; + name: string; + } - console.time("Check for Duplicate Routes"); - // check if there are any other networks with the same routes. - let duplicateRoutes: DuplicateRoutes[] = []; - if (targetIPs.length > 0) { - duplicateRoutes = await ctx.prisma.$queryRaw` + console.time("Check for Duplicate Routes"); + // check if there are any other networks with the same routes. + let duplicateRoutes: DuplicateRoutes[] = []; + if (targetIPs.length > 0) { + duplicateRoutes = await ctx.prisma.$queryRaw` SELECT "authorId", "routes", "name", "nwid" FROM "network" WHERE "authorId" = ${ctx.session.user.id} @@ -215,40 +222,44 @@ export const networkRouter = createTRPCRouter({ ) AND "nwid" != ${input.nwid}; `; - } else { - // Handle the case when targetIPs is empty - duplicateRoutes = []; - } + } else { + // Handle the case when targetIPs is empty + duplicateRoutes = []; + } - // Extract duplicated IPs - const duplicatedIPs = duplicateRoutes.flatMap((network) => - network.routes - .filter((route) => targetIPs.includes(route.target)) - .map((route) => route.target), - ); - console.timeEnd("Check for Duplicate Routes"); + // Extract duplicated IPs + const duplicatedIPs = duplicateRoutes.flatMap((network) => + network.routes + .filter((route) => targetIPs.includes(route.target)) + .map((route) => route.target), + ); + console.timeEnd("Check for Duplicate Routes"); - // Remove duplicates from the list of duplicated IPs - const uniqueDuplicatedIPs = [...new Set(duplicatedIPs)]; + // Remove duplicates from the list of duplicated IPs + const uniqueDuplicatedIPs = [...new Set(duplicatedIPs)]; - // Convert the map back to an array of merged members - const mergedMembers = [...mergedMembersMap.values()]; + // Convert the map back to an array of merged members + const mergedMembers = [...mergedMembersMap.values()]; - console.timeEnd("Total time for loading Networks Details"); - // Construct the final response object - return { - network: { - ...ztControllerResponse?.network, - ...networkFromDatabase, - cidr: cidrOptions, - duplicateRoutes: duplicateRoutes.map((network) => ({ - ...network, - duplicatedIPs: uniqueDuplicatedIPs, - })), - }, - members: mergedMembers as MemberEntity[], - zombieMembers, - }; + console.timeEnd("Total time for loading Networks Details"); + // Construct the final response object + return { + network: { + ...ztControllerResponse?.network, + ...networkFromDatabase, + cidr: cidrOptions, + duplicateRoutes: duplicateRoutes.map((network) => ({ + ...network, + duplicatedIPs: uniqueDuplicatedIPs, + })), + }, + members: mergedMembers as MemberEntity[], + zombieMembers, + }; + } catch (error) { + console.timeEnd("Total time for loading Networks Details"); + console.error(error); + } }), deleteNetwork: protectedProcedure .input( From 3340945c47c4531ad75df878e7678ecd5c8c3e39 Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Fri, 12 Jul 2024 05:46:57 +0000 Subject: [PATCH 5/8] limit the controller requests --- package-lock.json | 102 ++++++++++++++++++++--- package.json | 3 +- src/server/api/routers/networkRouter.ts | 24 ++---- src/server/api/services/memberService.ts | 13 ++- 4 files changed, 109 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index d138588d..2bc9e684 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "next-intl": "3.1.4", "next-themes": "^0.2.1", "nodemailer": "^6.9.9", + "p-limit": "^6.1.0", "pug": "^3.0.3", "qrcode.react": "^3.1.0", "react": "18.2.0", @@ -5619,6 +5620,33 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-circus": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", @@ -5662,6 +5690,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-circus/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -5682,6 +5725,18 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/jest-circus/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -6366,6 +6421,33 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -7529,15 +7611,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", "dependencies": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9865,12 +9946,11 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 84598532..009456f5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "next-intl": "3.1.4", "next-themes": "^0.2.1", "nodemailer": "^6.9.9", + "p-limit": "^6.1.0", "pug": "^3.0.3", "qrcode.react": "^3.1.0", "react": "18.2.0", @@ -105,4 +106,4 @@ "prisma": { "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } -} \ No newline at end of file +} diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 639e74ff..74b4fd64 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -75,7 +75,6 @@ export const networkRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - console.time("Total time for loading Networks Details"); try { if (input.central) { return await ztController.central_network_detail( @@ -84,7 +83,6 @@ export const networkRouter = createTRPCRouter({ input.central, ); } - console.time("Loading Network from Database"); // First, retrieve the network with organization details let networkFromDatabase = await ctx.prisma.network.findUnique({ where: { @@ -98,9 +96,8 @@ export const networkRouter = createTRPCRouter({ if (!networkFromDatabase) { return throwError("Network not found!"); } - console.timeEnd("Loading Network from Database"); - console.time("Fetch Organization Details"); // Check if the user is the author of the network or part of the associated organization + // Check if the user is the author of the network or part of the associated organization const isAuthor = networkFromDatabase.authorId === ctx.session.user.id; const isMemberOfOrganization = networkFromDatabase.organizationId && @@ -116,9 +113,7 @@ export const networkRouter = createTRPCRouter({ if (!isAuthor && !isMemberOfOrganization) { return throwError("You do not have access to this network!"); } - console.timeEnd("Fetch Organization Details"); - console.time("Loading Network Details from Controller"); /** * Response from the ztController.local_network_detail method. * @type {Promise} @@ -128,9 +123,9 @@ export const networkRouter = createTRPCRouter({ .catch((err: APIError) => { throwError(`${err.message}`); }); - console.timeEnd("Loading Network Details from Controller"); + if (!ztControllerResponse) return throwError("Failed to get network details!"); - console.time("Syncing Member Peers and Status"); + /** * Syncs member peers and status. */ @@ -139,9 +134,7 @@ export const networkRouter = createTRPCRouter({ input.nwid, ztControllerResponse.members, ); - console.timeEnd("Syncing Member Peers and Status"); - console.time("Fetch Zombie Members"); /** * Fetches zombie members. */ @@ -149,7 +142,6 @@ export const networkRouter = createTRPCRouter({ input.nwid, ztControllerResponse.members, ); - console.timeEnd("Fetch Zombie Members"); // Generate CIDR options for IP configuration const { cidrOptions } = IPv4gen(null, []); @@ -163,7 +155,6 @@ export const networkRouter = createTRPCRouter({ mergedMembersMap.set(member.id, member); } - console.time("Fetch Members from Database"); // Fetch members from the database for a given network ID where the members are not deleted const databaseMembers = await ctx.prisma.network_members.findMany({ where: { @@ -171,14 +162,14 @@ export const networkRouter = createTRPCRouter({ deleted: false, }, }); - console.timeEnd("Fetch Members from Database"); + // Process databaseMembers for (const member of databaseMembers) { if (!mergedMembersMap.has(member.id)) { mergedMembersMap.set(member.id, member); } } - console.time("Update Network Routes in Database"); + // if the networkFromDatabase.routes is not equal to the ztControllerResponse.routes, update the networkFromDatabase.routes if ( JSON.stringify(networkFromDatabase.routes) !== @@ -200,14 +191,12 @@ export const networkRouter = createTRPCRouter({ const targetIPs = ztControllerResponse.network.routes.map( (route) => route.target, ); - console.timeEnd("Update Network Routes in Database"); interface DuplicateRoutes { authorId: string; routes: RoutesEntity[]; name: string; } - console.time("Check for Duplicate Routes"); // check if there are any other networks with the same routes. let duplicateRoutes: DuplicateRoutes[] = []; if (targetIPs.length > 0) { @@ -233,7 +222,6 @@ export const networkRouter = createTRPCRouter({ .filter((route) => targetIPs.includes(route.target)) .map((route) => route.target), ); - console.timeEnd("Check for Duplicate Routes"); // Remove duplicates from the list of duplicated IPs const uniqueDuplicatedIPs = [...new Set(duplicatedIPs)]; @@ -241,7 +229,6 @@ export const networkRouter = createTRPCRouter({ // Convert the map back to an array of merged members const mergedMembers = [...mergedMembersMap.values()]; - console.timeEnd("Total time for loading Networks Details"); // Construct the final response object return { network: { @@ -257,7 +244,6 @@ export const networkRouter = createTRPCRouter({ zombieMembers, }; } catch (error) { - console.timeEnd("Total time for loading Networks Details"); console.error(error); } }), diff --git a/src/server/api/services/memberService.ts b/src/server/api/services/memberService.ts index 8184f4da..7711c585 100644 --- a/src/server/api/services/memberService.ts +++ b/src/server/api/services/memberService.ts @@ -7,6 +7,9 @@ import { sendWebhook } from "~/utils/webhook"; import { HookType, MemberJoined } from "~/types/webhooks"; import { throwError } from "~/server/helpers/errorHandler"; import { network_members } from "@prisma/client"; +import pLimit from "p-limit"; + +const limit = pLimit(5); /** * syncMemberPeersAndStatus @@ -21,12 +24,18 @@ export const syncMemberPeersAndStatus = async ( ztMembers: MemberEntity[], ) => { if (ztMembers.length === 0) return []; + + // Create a list of limited peer requests to control the number of concurrent requests + const limitedPeerRequests = ztMembers.map((ztMember) => + limit(() => ztController.peer(ctx, ztMember.address)), + ); + const updatedMembers = await Promise.all( - ztMembers.map(async (ztMember) => { + ztMembers.map(async (ztMember, idx) => { // TODO currently there is no way to distinguish peers by network id, so we have to fetch all peers // this will make the node active in all networks it is part of if it is active in one of them. // Should open a issue at ZeroTier - const peers = await ztController.peer(ctx, ztMember.address).catch(() => null); + const peers = (await limitedPeerRequests[idx]) as unknown as Peers; // Retrieve the member from the database const dbMember = await retrieveActiveMemberFromDatabase(nwid, ztMember.id); From 89afd3085c2f1a9f890eddb82543f96d3c333235 Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Fri, 12 Jul 2024 06:33:41 +0000 Subject: [PATCH 6/8] member peers --- src/server/api/services/memberService.ts | 51 ++++++++++++++++++------ src/types/local/member.d.ts | 8 +--- src/utils/ztApi.ts | 4 +- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/server/api/services/memberService.ts b/src/server/api/services/memberService.ts index 7711c585..89ce9a33 100644 --- a/src/server/api/services/memberService.ts +++ b/src/server/api/services/memberService.ts @@ -1,15 +1,42 @@ import { UserContext } from "~/types/ctx"; import { MemberEntity, Peers } from "~/types/local/member"; -import * as ztController from "~/utils/ztApi"; import { determineConnectionStatus } from "../utils/memberUtils"; +import * as ztController from "~/utils/ztApi"; import { prisma } from "~/server/db"; import { sendWebhook } from "~/utils/webhook"; import { HookType, MemberJoined } from "~/types/webhooks"; import { throwError } from "~/server/helpers/errorHandler"; import { network_members } from "@prisma/client"; -import pLimit from "p-limit"; -const limit = pLimit(5); +// create a dummy array of 30 member +// export const dummyMembers = Array.from({ length: 10 }, (_, i) => ({ +// activeBridge: false, +// address: "efcc1b0947", +// authenticationExpiryTime: 0, +// authorized: true, +// capabilities: [], +// creationTime: 1719608117618, +// id: "efcc1b0947", +// identity: +// "efcc1b0947:0:0152e4bbdf517c0218ff096db3195f65b7f66cb544d6506cb13fe01f9851212652f2cabe6133bd0e83e0c2930c7db23a2e14574782256e94e831711d04c48979", +// ipAssignments: [], +// lastAuthorizedCredential: null, +// lastAuthorizedCredentialType: "api", +// lastAuthorizedTime: 1720759395643, +// lastDeauthorizedTime: 0, +// noAutoAssignIps: false, +// nwid: "fc6b977f183f6a65", +// objtype: "member", +// remoteTraceLevel: 0, +// remoteTraceTarget: null, +// revision: 2, +// ssoExempt: false, +// tags: [], +// vMajor: -1, +// vMinor: -1, +// vProto: -1, +// vRev: -1, +// })); /** * syncMemberPeersAndStatus @@ -24,25 +51,23 @@ export const syncMemberPeersAndStatus = async ( ztMembers: MemberEntity[], ) => { if (ztMembers.length === 0) return []; - - // Create a list of limited peer requests to control the number of concurrent requests - const limitedPeerRequests = ztMembers.map((ztMember) => - limit(() => ztController.peer(ctx, ztMember.address)), - ); + // get peers + const controllerPeers = await ztController.peers(ctx); const updatedMembers = await Promise.all( - ztMembers.map(async (ztMember, idx) => { + ztMembers.map(async (ztMember) => { // TODO currently there is no way to distinguish peers by network id, so we have to fetch all peers // this will make the node active in all networks it is part of if it is active in one of them. // Should open a issue at ZeroTier - const peers = (await limitedPeerRequests[idx]) as unknown as Peers; + const peers = controllerPeers.filter( + (peer) => peer.address === ztMember.address, + )[0]; // Retrieve the member from the database const dbMember = await retrieveActiveMemberFromDatabase(nwid, ztMember.id); // Find the active preferred path in the peers object const activePreferredPath = findActivePreferredPeerPath(peers); - const { physicalAddress, ...restOfDbMembers } = dbMember || {}; // Merge the data from the database with the data from Controller @@ -50,7 +75,7 @@ export const syncMemberPeersAndStatus = async ( ...restOfDbMembers, ...ztMember, physicalAddress: activePreferredPath?.address ?? physicalAddress, - peers, + peers: peers || {}, } as MemberEntity; // Update the connection status @@ -107,7 +132,7 @@ export const syncMemberPeersAndStatus = async ( * @param peers - The peers object containing paths. * @returns The active preferred path, or undefined if not found. */ -const findActivePreferredPeerPath = (peers: Peers) => { +const findActivePreferredPeerPath = (peers: Peers | null) => { if (!peers || typeof peers !== "object" || !Array.isArray(peers.paths)) { return null; } diff --git a/src/types/local/member.d.ts b/src/types/local/member.d.ts index e423a732..c0c92ab8 100644 --- a/src/types/local/member.d.ts +++ b/src/types/local/member.d.ts @@ -67,22 +67,16 @@ interface CentralMemberConfig { } export interface Peers { - active: boolean; address: string; isBonded: boolean; latency: number; - lastReceive: number; - lastSend: number; - localSocket?: number; paths?: Paths[]; role: string; version: string; - physicalAddress: string; + physicalAddress?: string; versionMajor: number; versionMinor: number; versionRev: number; - preferred: boolean; - trustedPathId: number; } export interface Paths { diff --git a/src/utils/ztApi.ts b/src/utils/ztApi.ts index f64c7fa1..e899ca6d 100644 --- a/src/utils/ztApi.ts +++ b/src/utils/ztApi.ts @@ -625,7 +625,7 @@ export const member_details = async ( // Get all peers // https://docs.zerotier.com/service/v1/#operation/getPeers -export const peers = async (ctx: UserContext): Promise => { +export const peers = async (ctx: UserContext): Promise => { // get headers based on local or central api const { localControllerUrl } = await getOptions(ctx, false); @@ -635,7 +635,7 @@ export const peers = async (ctx: UserContext): Promise => { try { const response: AxiosResponse = await axios.get(addr, { headers }); - return response.data as ZTControllerGetPeer; + return response.data as ZTControllerGetPeer[]; } catch (error) { const message = `${error} (peers)`; throw new APIError(message, error as AxiosError); From 05c7971ecdf36a239c3675e051bdae6571e288c5 Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Fri, 12 Jul 2024 14:58:12 +0000 Subject: [PATCH 7/8] cleanup --- src/server/api/routers/networkRouter.ts | 1 + src/server/api/services/memberService.ts | 30 ------------------------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 74b4fd64..398fe1a6 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -245,6 +245,7 @@ export const networkRouter = createTRPCRouter({ }; } catch (error) { console.error(error); + return throwError("Failed to get network details! See logs for more details."); } }), deleteNetwork: protectedProcedure diff --git a/src/server/api/services/memberService.ts b/src/server/api/services/memberService.ts index 89ce9a33..7ecbe94d 100644 --- a/src/server/api/services/memberService.ts +++ b/src/server/api/services/memberService.ts @@ -8,36 +8,6 @@ import { HookType, MemberJoined } from "~/types/webhooks"; import { throwError } from "~/server/helpers/errorHandler"; import { network_members } from "@prisma/client"; -// create a dummy array of 30 member -// export const dummyMembers = Array.from({ length: 10 }, (_, i) => ({ -// activeBridge: false, -// address: "efcc1b0947", -// authenticationExpiryTime: 0, -// authorized: true, -// capabilities: [], -// creationTime: 1719608117618, -// id: "efcc1b0947", -// identity: -// "efcc1b0947:0:0152e4bbdf517c0218ff096db3195f65b7f66cb544d6506cb13fe01f9851212652f2cabe6133bd0e83e0c2930c7db23a2e14574782256e94e831711d04c48979", -// ipAssignments: [], -// lastAuthorizedCredential: null, -// lastAuthorizedCredentialType: "api", -// lastAuthorizedTime: 1720759395643, -// lastDeauthorizedTime: 0, -// noAutoAssignIps: false, -// nwid: "fc6b977f183f6a65", -// objtype: "member", -// remoteTraceLevel: 0, -// remoteTraceTarget: null, -// revision: 2, -// ssoExempt: false, -// tags: [], -// vMajor: -1, -// vMinor: -1, -// vProto: -1, -// vRev: -1, -// })); - /** * syncMemberPeersAndStatus * Synchronizes the peers and connection status of the given members. From f3dca7b8c0eb3f8e90c547173c1d7b28bd2b064b Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Fri, 12 Jul 2024 15:09:19 +0000 Subject: [PATCH 8/8] error handling --- src/server/api/routers/networkRouter.ts | 267 ++++++++++++------------ 1 file changed, 128 insertions(+), 139 deletions(-) diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 398fe1a6..62c05a45 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -75,132 +75,125 @@ export const networkRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - try { - if (input.central) { - return await ztController.central_network_detail( - ctx, - input.nwid, - input.central, - ); - } - // First, retrieve the network with organization details - let networkFromDatabase = await ctx.prisma.network.findUnique({ + if (input.central) { + return await ztController.central_network_detail(ctx, input.nwid, input.central); + } + // First, retrieve the network with organization details + let networkFromDatabase = await ctx.prisma.network.findUnique({ + where: { + nwid: input.nwid, + }, + include: { + organization: true, + }, + }); + + if (!networkFromDatabase) { + return throwError("Network not found!"); + } + + // Check if the user is the author of the network or part of the associated organization + const isAuthor = networkFromDatabase.authorId === ctx.session.user.id; + const isMemberOfOrganization = + networkFromDatabase.organizationId && + (await ctx.prisma.organization.findFirst({ where: { - nwid: input.nwid, - }, - include: { - organization: true, + id: networkFromDatabase.organizationId, + users: { + some: { id: ctx.session.user.id }, + }, }, - }); + })); - if (!networkFromDatabase) { - return throwError("Network not found!"); - } + if (!isAuthor && !isMemberOfOrganization) { + return throwError("You do not have access to this network!"); + } - // Check if the user is the author of the network or part of the associated organization - const isAuthor = networkFromDatabase.authorId === ctx.session.user.id; - const isMemberOfOrganization = - networkFromDatabase.organizationId && - (await ctx.prisma.organization.findFirst({ - where: { - id: networkFromDatabase.organizationId, - users: { - some: { id: ctx.session.user.id }, - }, - }, - })); + /** + * Response from the ztController.local_network_detail method. + * @type {Promise} + */ + const ztControllerResponse = await ztController + .local_network_detail(ctx, networkFromDatabase.nwid, false) + .catch((err: APIError) => { + throwError(`${err.message}`); + }); - if (!isAuthor && !isMemberOfOrganization) { - return throwError("You do not have access to this network!"); - } + if (!ztControllerResponse) return throwError("Failed to get network details!"); - /** - * Response from the ztController.local_network_detail method. - * @type {Promise} - */ - const ztControllerResponse = await ztController - .local_network_detail(ctx, networkFromDatabase.nwid, false) - .catch((err: APIError) => { - throwError(`${err.message}`); - }); + /** + * Syncs member peers and status. + */ + const membersWithStatusAndPeers = await syncMemberPeersAndStatus( + ctx, + input.nwid, + ztControllerResponse.members, + ); - if (!ztControllerResponse) return throwError("Failed to get network details!"); + /** + * Fetches zombie members. + */ + const zombieMembers = await fetchZombieMembers( + input.nwid, + ztControllerResponse.members, + ); - /** - * Syncs member peers and status. - */ - const membersWithStatusAndPeers = await syncMemberPeersAndStatus( - ctx, - input.nwid, - ztControllerResponse.members, - ); + // Generate CIDR options for IP configuration + const { cidrOptions } = IPv4gen(null, []); - /** - * Fetches zombie members. - */ - const zombieMembers = await fetchZombieMembers( - input.nwid, - ztControllerResponse.members, - ); + /** + * Merging logic to ensure that members who only exist in local database ( added manually ) are also included in the response + * Create a map to store members by their id for efficient lookup + */ + const mergedMembersMap = new Map(); + for (const member of membersWithStatusAndPeers) { + mergedMembersMap.set(member.id, member); + } - // Generate CIDR options for IP configuration - const { cidrOptions } = IPv4gen(null, []); + // Fetch members from the database for a given network ID where the members are not deleted + const databaseMembers = await ctx.prisma.network_members.findMany({ + where: { + nwid: input.nwid, + deleted: false, + }, + }); - /** - * Merging logic to ensure that members who only exist in local database ( added manually ) are also included in the response - * Create a map to store members by their id for efficient lookup - */ - const mergedMembersMap = new Map(); - for (const member of membersWithStatusAndPeers) { + // Process databaseMembers + for (const member of databaseMembers) { + if (!mergedMembersMap.has(member.id)) { mergedMembersMap.set(member.id, member); } + } - // Fetch members from the database for a given network ID where the members are not deleted - const databaseMembers = await ctx.prisma.network_members.findMany({ + // if the networkFromDatabase.routes is not equal to the ztControllerResponse.routes, update the networkFromDatabase.routes + if ( + JSON.stringify(networkFromDatabase.routes) !== + JSON.stringify(ztControllerResponse.network.routes) + ) { + networkFromDatabase = await ctx.prisma.network.update({ where: { nwid: input.nwid, - deleted: false, + }, + data: { + routes: ztControllerResponse.network.routes as string[], + }, + include: { + organization: true, }, }); + } + // check if there is other network using same routes and return a notification + const targetIPs = ztControllerResponse.network.routes.map((route) => route.target); + interface DuplicateRoutes { + authorId: string; + routes: RoutesEntity[]; + name: string; + } - // Process databaseMembers - for (const member of databaseMembers) { - if (!mergedMembersMap.has(member.id)) { - mergedMembersMap.set(member.id, member); - } - } - - // if the networkFromDatabase.routes is not equal to the ztControllerResponse.routes, update the networkFromDatabase.routes - if ( - JSON.stringify(networkFromDatabase.routes) !== - JSON.stringify(ztControllerResponse.network.routes) - ) { - networkFromDatabase = await ctx.prisma.network.update({ - where: { - nwid: input.nwid, - }, - data: { - routes: ztControllerResponse.network.routes as string[], - }, - include: { - organization: true, - }, - }); - } - // check if there is other network using same routes and return a notification - const targetIPs = ztControllerResponse.network.routes.map( - (route) => route.target, - ); - interface DuplicateRoutes { - authorId: string; - routes: RoutesEntity[]; - name: string; - } - - // check if there are any other networks with the same routes. - let duplicateRoutes: DuplicateRoutes[] = []; - if (targetIPs.length > 0) { - duplicateRoutes = await ctx.prisma.$queryRaw` + // check if there are any other networks with the same routes. + let duplicateRoutes: DuplicateRoutes[] = []; + if (targetIPs.length > 0) { + duplicateRoutes = await ctx.prisma.$queryRaw` SELECT "authorId", "routes", "name", "nwid" FROM "network" WHERE "authorId" = ${ctx.session.user.id} @@ -211,42 +204,38 @@ export const networkRouter = createTRPCRouter({ ) AND "nwid" != ${input.nwid}; `; - } else { - // Handle the case when targetIPs is empty - duplicateRoutes = []; - } + } else { + // Handle the case when targetIPs is empty + duplicateRoutes = []; + } - // Extract duplicated IPs - const duplicatedIPs = duplicateRoutes.flatMap((network) => - network.routes - .filter((route) => targetIPs.includes(route.target)) - .map((route) => route.target), - ); + // Extract duplicated IPs + const duplicatedIPs = duplicateRoutes.flatMap((network) => + network.routes + .filter((route) => targetIPs.includes(route.target)) + .map((route) => route.target), + ); - // Remove duplicates from the list of duplicated IPs - const uniqueDuplicatedIPs = [...new Set(duplicatedIPs)]; + // Remove duplicates from the list of duplicated IPs + const uniqueDuplicatedIPs = [...new Set(duplicatedIPs)]; - // Convert the map back to an array of merged members - const mergedMembers = [...mergedMembersMap.values()]; + // Convert the map back to an array of merged members + const mergedMembers = [...mergedMembersMap.values()]; - // Construct the final response object - return { - network: { - ...ztControllerResponse?.network, - ...networkFromDatabase, - cidr: cidrOptions, - duplicateRoutes: duplicateRoutes.map((network) => ({ - ...network, - duplicatedIPs: uniqueDuplicatedIPs, - })), - }, - members: mergedMembers as MemberEntity[], - zombieMembers, - }; - } catch (error) { - console.error(error); - return throwError("Failed to get network details! See logs for more details."); - } + // Construct the final response object + return { + network: { + ...ztControllerResponse?.network, + ...networkFromDatabase, + cidr: cidrOptions, + duplicateRoutes: duplicateRoutes.map((network) => ({ + ...network, + duplicatedIPs: uniqueDuplicatedIPs, + })), + }, + members: mergedMembers as MemberEntity[], + zombieMembers, + }; }), deleteNetwork: protectedProcedure .input(