diff --git a/docs/docs/Rest Api/_source/networkMember.yml b/docs/docs/Rest Api/_source/networkMember.yml index 11e7ab5f..36d047fd 100644 --- a/docs/docs/Rest Api/_source/networkMember.yml +++ b/docs/docs/Rest Api/_source/networkMember.yml @@ -69,7 +69,6 @@ paths: properties: message: type: string - /network/{networkId}/member/{memberId}: post: summary: Modify a network member @@ -133,6 +132,59 @@ paths: properties: error: type: string + 500: + description: Internal server error + content: + application/json: + schema: + type: object + properties: + message: + type: string + delete: + summary: Delete a network member + # description: Delete a network member + operationId: deleteNetworkMember + parameters: + - in: path + name: networkId + required: true + schema: + type: string + description: ID of the network + - in: path + name: memberId + required: true + schema: + type: string + description: ID of the member + responses: + 200: + description: Network Response + content: + application/json: + schema: + $ref: '../_schema/NetworkResponse.yml#/NetworkResponse' + example: + $ref: '../_example/NetworkExample.yml#/NetworkArrayExample' + 401: + description: Unauthorized + content: + application/json: + schema: + type: object + properties: + error: + type: string + 429: + description: Rate limit exceeded + content: + application/json: + schema: + type: object + properties: + error: + type: string 500: description: Internal server error content: diff --git a/src/pages/api/__tests__/v1/networkMembers/updateMember.test.ts b/src/pages/api/__tests__/v1/networkMembers/updateMember.test.ts index 9f8cc80a..4d65bf81 100644 --- a/src/pages/api/__tests__/v1/networkMembers/updateMember.test.ts +++ b/src/pages/api/__tests__/v1/networkMembers/updateMember.test.ts @@ -160,8 +160,8 @@ describe("Update Network Members", () => { ); }); - it("should allow only POST method", async () => { - const methods = ["GET", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]; + it("should allow only POST and DELETE method", async () => { + const methods = ["GET", "PUT", "PATCH", "OPTIONS", "HEAD"]; const req = {} as NextApiRequest; const res = createMockRes(); diff --git a/src/pages/api/v1/network/[id]/member/[memberId]/index.ts b/src/pages/api/v1/network/[id]/member/[memberId]/index.ts index 5fbb6333..bcfdd4d0 100644 --- a/src/pages/api/v1/network/[id]/member/[memberId]/index.ts +++ b/src/pages/api/v1/network/[id]/member/[memberId]/index.ts @@ -43,7 +43,10 @@ export default async function apiNetworkUpdateMembersHandler( // create a switch based on the HTTP method switch (req.method) { case "POST": - await POST_networkUpdateMembers(req, res); + await POST_updateNetworkMember(req, res); + break; + case "DELETE": + await POST_deleteNetworkMember(req, res); break; default: // Method Not Allowed @@ -52,7 +55,14 @@ export default async function apiNetworkUpdateMembersHandler( } } -const POST_networkUpdateMembers = async (req: NextApiRequest, res: NextApiResponse) => { +/** + * Handles the POST request to update network members. + * + * @param req - The NextApiRequest object. + * @param res - The NextApiResponse object. + * @returns A JSON response indicating the success or failure of the update operation. + */ +const POST_updateNetworkMember = async (req: NextApiRequest, res: NextApiResponse) => { const apiKey = req.headers["x-ztnet-auth"] as string; const networkId = req.query?.id as string; const memberId = req.query?.memberId as string; @@ -199,3 +209,82 @@ const POST_networkUpdateMembers = async (req: NextApiRequest, res: NextApiRespon return res.status(500).json({ message: "Internal server error" }); } }; + +/** + * Handles the HTTP DELETE request to delete a member from a network. + * + * @param req - The NextApiRequest object representing the incoming request. + * @param res - The NextApiResponse object representing the outgoing response. + * @returns A JSON response indicating the success or failure of the operation. + */ +const POST_deleteNetworkMember = async (req: NextApiRequest, res: NextApiResponse) => { + const apiKey = req.headers["x-ztnet-auth"] as string; + const networkId = req.query?.id as string; + const memberId = req.query?.memberId as string; + + let decryptedData: { userId: string; name?: string }; + try { + decryptedData = await decryptAndVerifyToken({ apiKey }); + } catch (error) { + return res.status(401).json({ error: error.message }); + } + + // Check if the networkId exists + if (!networkId) { + return res.status(400).json({ error: "Network ID is required" }); + } + + // Check if the networkId exists + if (!memberId) { + return res.status(400).json({ error: "Member ID is required" }); + } + + // assemble the context object + const ctx = { + session: { + user: { + id: decryptedData.userId as string, + }, + }, + prisma, + wss: null, + }; + + try { + // make sure the member is valid + const network = await prisma.network.findUnique({ + where: { nwid: networkId, authorId: decryptedData.userId }, + include: { + networkMembers: { + where: { id: memberId }, + }, + }, + }); + + if (!network?.networkMembers || network.networkMembers.length === 0) { + return res + .status(401) + .json({ error: "Member or Network not found or access denied." }); + } + + // @ts-expect-error + const caller = appRouter.createCaller(ctx); + const networkAndMembers = await caller.networkMember.stash({ + nwid: networkId, + id: memberId, + }); + + return res.status(200).json(networkAndMembers); + } catch (cause) { + if (cause instanceof TRPCError) { + const httpCode = getHTTPStatusCodeFromError(cause); + try { + const parsedErrors = JSON.parse(cause.message); + return res.status(httpCode).json({ cause: parsedErrors }); + } catch (_error) { + return res.status(httpCode).json({ error: cause.message }); + } + } + return res.status(500).json({ message: "Internal server error" }); + } +}; diff --git a/src/server/api/routers/memberRouter.ts b/src/server/api/routers/memberRouter.ts index 52ced48d..2bb42743 100644 --- a/src/server/api/routers/memberRouter.ts +++ b/src/server/api/routers/memberRouter.ts @@ -265,7 +265,7 @@ export const networkMemberRouter = createTRPCRouter({ throwError(error.message); } - if (input.central) return updatedMember; + return updatedMember; }), Tags: protectedProcedure .input( @@ -466,18 +466,24 @@ export const networkMemberRouter = createTRPCRouter({ const caller = networkMemberRouter.createCaller(ctx); //user needs to be de-authorized before deleted. // adding try catch to prevent error if user is not part of the network but still in the database. + let response; try { - await caller.Update({ + response = await caller.Update({ memberId: input.id, nwid: input.nwid, - updateParams: { authorized: false, ipAssignments: [] }, + updateParams: { + authorized: false, + ipAssignments: [], + tags: [], + capabilities: [], + }, }); } catch (error) { console.error(error); } // Set member with deleted status in database. - const memberUpdate = await ctx.prisma.network + await ctx.prisma.network .update({ where: { nwid: input.nwid, @@ -492,14 +498,6 @@ export const networkMemberRouter = createTRPCRouter({ }, }, }, - include: { - networkMembers: { - where: { - id: input.id, - deleted: false, - }, - }, - }, }) // biome-ignore lint/suspicious/noConsoleLog: .catch((err: string) => console.log(err)); @@ -520,7 +518,7 @@ export const networkMemberRouter = createTRPCRouter({ throwError(error.message); } - return memberUpdate; + return response; }), delete: protectedProcedure .input(