From a1432788cdbd82440ce8da4cc3c2dc5655a7ae24 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 7 Nov 2024 11:55:33 +0000 Subject: [PATCH] fixing delete --- .../[portfolioId]/permissions/route.ts | 80 ++++++++++ src/app/api/portfolio/[portfolioId]/route.ts | 143 ++++++++++++++---- .../settings/PortfolioSettings.tsx | 68 +++++---- 3 files changed, 231 insertions(+), 60 deletions(-) create mode 100644 src/app/api/portfolio/[portfolioId]/permissions/route.ts diff --git a/src/app/api/portfolio/[portfolioId]/permissions/route.ts b/src/app/api/portfolio/[portfolioId]/permissions/route.ts new file mode 100644 index 00000000..d70652e4 --- /dev/null +++ b/src/app/api/portfolio/[portfolioId]/permissions/route.ts @@ -0,0 +1,80 @@ +import { db } from "@/app/db/db"; +import { NextRequest, NextResponse } from "next/server"; +import { portfolioUsers } from "@/app/db/schema/portfolio"; +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; + +const PermissionsBodySchema = z.object({ + userId: z.string(), + action: z.enum(["delete", "update"]), +}); + +export async function POST( + request: NextRequest, + { params }: { params: { portfolioId: string } } +) { + // This endpoint lives at portfolio/{portfolioId}/permissions and will return the permissions level for a given portfolio + // Call this endpoint with a) userId, b) portfolioId, c) an action and this api will tell you if that person can do that thing + + const body = await request.json(); + let validatedBody; + + try { + validatedBody = PermissionsBodySchema.parse(body); + } catch (error) { + console.error("Invalid input: ", error); + return new NextResponse(JSON.stringify({ msg: "Invalid input" }), { + status: 400, + }); + } + + const action = validatedBody.action; + const userId = validatedBody.userId; + const portfolioId = params.portfolioId; + + const existingPortfolioPermissions = await db.query.portfolioUsers.findFirst({ + where: and( + eq(portfolioUsers.portfolioId, BigInt(portfolioId)), + eq(portfolioUsers.userId, BigInt(userId)) + ), + }); + + if (!existingPortfolioPermissions) { + return new NextResponse( + JSON.stringify({ error: "Portfolio not found or unauthorized" }), + { status: 404 } + ); + } + + const role = existingPortfolioPermissions.role; + + let permitted; + + // If the action is a delete, we only allow an admin or creator to delete + if (action === "delete") { + if (role === "admin" || role === "creator") { + permitted = true; + } else { + permitted = false; + } + } + + // If the action is an update, we allow an individual admin, creator or write role to update + if (action === "update") { + if (role === "admin" || role === "creator" || role === "write") { + permitted = true; + } else { + permitted = false; + } + } + + // Returning a successful response + return new NextResponse( + JSON.stringify({ + permitted: permitted, + }), + { + status: 200, + } + ); +} diff --git a/src/app/api/portfolio/[portfolioId]/route.ts b/src/app/api/portfolio/[portfolioId]/route.ts index d551c91b..9fbb9de2 100644 --- a/src/app/api/portfolio/[portfolioId]/route.ts +++ b/src/app/api/portfolio/[portfolioId]/route.ts @@ -1,24 +1,37 @@ import { db } from "@/app/db/db"; import { NextRequest, NextResponse } from "next/server"; import { portfolio, portfolioUsers } from "@/app/db/schema/portfolio"; -import { and, eq, inArray } from "drizzle-orm"; -import { user } from "@/app/db/schema/users"; +import { + recommendation, + recommendationMaterials, + planRecommendations, + plan, + scenario, +} from "@/app/db/schema/recommendations"; +import { + propertyTargets, + propertyDetailsEpc, + property, +} from "@/app/db/schema/property"; +import { eq, inArray } from "drizzle-orm"; -export async function PUT(request: NextRequest) { +export async function PUT( + request: NextRequest, + { params }: { params: { portfolioId: string } } +) { const body = await request.json(); - console.log("HI WE MADE IT!!"); - console.log(body); + const portfolioId = params.portfolioId; - const portfolioId = body.portfolioId; + // We'll eventually veryify the user is authorized to update this portfolio const userId = body.userId; delete body.userId; - delete body.portfolioId; - - console.log(body); // Update the database - await db.update(portfolio).set(body).where(eq(portfolio.id, portfolioId)); + await db + .update(portfolio) + .set(body) + .where(eq(portfolio.id, BigInt(portfolioId))); // Returning a successful response return new NextResponse(JSON.stringify({}), { @@ -26,46 +39,110 @@ export async function PUT(request: NextRequest) { }); } -export async function DELETE(request: NextRequest) { - console.log("Incoming DELETE request:", request.method); +export async function DELETE( + request: NextRequest, + { params }: { params: { portfolioId: string } } +) { try { - // Parse the request body - const body = await request.json(); - console.log("DELETE Request Received", body); + const portfolioId = params.portfolioId; - const portfolioId = body.portfolioId; - const userId = body.userId; + // 1) Fetch the portfolio ids + // 2) Fetch the recommendation ids + // 3) Delete all entries from RecommendationMaterials for these recommendations + // 4) Delete all entries from PlanRecommendations that reference plans in the portfolio + // 5) Delete all Plans associated with the portfolio + // 6) Delete all Scenarios associated with the portfolio + // 7) Delete all Recommendations associated with the properties + // 8) Delete PropertyTargetsModel, PropertyDetailsEpcModel, and PropertyModel + // 9) Delete portfolioUsers + // 10) Then, we finally delete the portfolio!!! - // First verify the portfolio exists and belongs to this user - const existingPortfolio = await db.query.portfolio.findFirst({ - where: and( - eq(portfolioUsers.portfolioId, portfolioId), - eq(portfolioUsers.userId, userId) - ) + // Step 1) Fetch the property ids for the portfolio + const propertyIdsResponse = await db.query.property.findMany({ + columns: { id: true }, + where: eq(property.portfolioId, BigInt(portfolioId)), }); + const propertyIds = propertyIdsResponse.map((property) => property.id); - if (!existingPortfolio) { - return new NextResponse( - JSON.stringify({ error: "Portfolio not found or unauthorized" }), - { status: 404 } + // Step 2) Fetch the recommendation ids - filter the recommendation table, where propertyId is in the propertyIds + // if there are no prpoperty Ids, we make recommendationIds an empty array + let recommendationIds: bigint[] = []; + if (propertyIds.length) { + const recommendations = await db.query.recommendation.findMany({ + columns: { id: true }, + where: inArray(recommendation.propertyId, propertyIds), + }); + recommendationIds = recommendations.map( + (recommendation) => recommendation.id ); } - // Delete the portfolio + // Step 3) Delete all entries from RecommendationMaterials for these recommendations + if (recommendationIds.length) { + await db + .delete(recommendationMaterials) + .where( + inArray(recommendationMaterials.recommendationId, recommendationIds) + ); + } + + // Step 4) Delete all entries from PlanRecommendations that reference plans in the portfolio + // Get the plan ids + const plans = await db.query.plan.findMany({ + columns: { id: true }, + where: eq(plan.portfolioId, BigInt(portfolioId)), + }); + const planIds = plans.map((plan) => plan.id); + + if (planIds.length) { + await db + .delete(planRecommendations) + .where(inArray(planRecommendations.planId, planIds)); + } + + // Step 5) Delete all Plans associated with the portfolio + await db.delete(plan).where(eq(plan.portfolioId, BigInt(portfolioId))); + + // Step 6) Delete all Scenarios associated with the portfolio await db - .delete(portfolio) - .where(eq(portfolio.id, portfolioId)); + .delete(scenario) + .where(eq(scenario.portfolioId, BigInt(portfolioId))); + + // Step 7) Delete all Recommendations associated with the properties + if (propertyIds.length) { + await db + .delete(recommendation) + .where(inArray(recommendation.propertyId, propertyIds)); + } + + // Step 8) Delete PropertyTargets, PropertyDetailsEpc, and Property + await db + .delete(propertyTargets) + .where(eq(propertyTargets.portfolioId, BigInt(portfolioId))); + + await db + .delete(propertyDetailsEpc) + .where(eq(propertyDetailsEpc.portfolioId, BigInt(portfolioId))); + + await db + .delete(property) + .where(eq(property.portfolioId, BigInt(portfolioId))); + + await db + .delete(portfolioUsers) + .where(eq(portfolioUsers.portfolioId, BigInt(portfolioId))); + + await db.delete(portfolio).where(eq(portfolio.id, BigInt(portfolioId))); // Return success response return new NextResponse( - JSON.stringify({ message: "Portfolio successfully deleted" }), + JSON.stringify({ message: "Portfolio successfully deleted" }), { status: 200 } ); - } catch (error) { console.error("Error deleting portfolio:", error); return new NextResponse( - JSON.stringify({ error: "Your API has Failed to delete the portfolio" }), + JSON.stringify({ error: "Your API has Failed to delete the portfolio" }), { status: 500 } ); } diff --git a/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx b/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx index ae6a3d36..c667b238 100644 --- a/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx @@ -70,7 +70,6 @@ type updateSettingsArgs = { type bodyType = { userId: string; - portfolioId: string; name?: string; budget?: number | string; goal?: string; @@ -92,7 +91,6 @@ const updateSettings = async ({ const body: bodyType = { userId: userId.toString(), - portfolioId: portfolioId, }; if (name) { @@ -128,25 +126,53 @@ const updateSettings = async ({ return response.json(); }; -async function deletePortfolio({ userId, portfolioId }: { +async function deletePortfolio({ + userId, + portfolioId, +}: { userId: bigint; portfolioId: string; }) { try { - console.log("Attempting to DELETE portfolio by calling API:", { userId, portfolioId }); + console.log("Attempting to DELETE portfolio by calling API:", { + userId, + portfolioId, + }); + + // We'll check if the user is authorized to delete this portfolio + const permissionsReponse = await fetch( + `/api/portfolio/${portfolioId}/permissions`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userId: userId.toString(), + action: "delete", + }), + } + ); + + const permissionsData = await permissionsReponse.json(); + const permitted = permissionsData.permitted; + + // If the user is not permitted to delete the portfolio, we'll throw an error + if (!permitted) { + throw new Error("User is not permitted to delete this portfolio"); + } + const response = await fetch(`/api/portfolio/${portfolioId}`, { method: "DELETE", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - userId: userId.toString(), - portfolioId: portfolioId, - }), }); if (!response.ok) { - throw new Error("deletePortfolio has been called into action but utterly failed to do the API handoff"); + throw new Error( + "deletePortfolio has been called into action but utterly failed to do the API handoff" + ); } return await response.json(); @@ -165,7 +191,6 @@ export default function PortfolioSettings({ }) { // This is a client component so we can access the session directly const session = useSession(); - const router = useRouter(); const { mutate, isLoading } = useMutation(updateSettings, { @@ -181,11 +206,13 @@ export default function PortfolioSettings({ const { mutate: mutateDelete } = useMutation(deletePortfolio, { onSuccess: () => { setIsDeleteModalOpen(false); - console.log("Succesfully Deleted") - router.push('/home'); + router.push("/home"); }, onError: (error) => { - console.error("Because the API hand off failed, we're right back here at the mutation station", error); + console.error( + "Because the API hand off failed, we're right back here at the mutation station", + error + ); }, }); @@ -230,7 +257,7 @@ export default function PortfolioSettings({ userId, portfolioId, }); - console.log("succesfully called the mututate function") + console.log("succesfully called the mututate function"); } } @@ -303,19 +330,6 @@ export default function PortfolioSettings({ status: portfolioStatus, }); } - - // Delete function - - function handleDelete() { - mutate({ - userId, - portfolioId, - name: portfolioName, - budget: portfolioBudget, - goal: portfolioGoal, - status: portfolioStatus, - }); - } // HTML to render the page