-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Soft delete resources #223
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,8 +1,9 @@ | ||||||||||||||||||||||||||
import { NextResponse } from "next/server"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import { and, eq } from "@ctrlplane/db"; | ||||||||||||||||||||||||||
import { and, eq, isNull } from "@ctrlplane/db"; | ||||||||||||||||||||||||||
import { db } from "@ctrlplane/db/client"; | ||||||||||||||||||||||||||
import * as schema from "@ctrlplane/db/schema"; | ||||||||||||||||||||||||||
import { deleteResources } from "@ctrlplane/job-dispatch"; | ||||||||||||||||||||||||||
import { Permission } from "@ctrlplane/validators/auth"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import { authn, authz } from "~/app/api/v1/auth"; | ||||||||||||||||||||||||||
|
@@ -14,21 +15,23 @@ export const GET = request() | |||||||||||||||||||||||||
authz(async ({ can, extra }) => { | ||||||||||||||||||||||||||
const { workspaceId, identifier } = extra; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const target = await db.query.resource.findFirst({ | ||||||||||||||||||||||||||
// we don't check deletedAt as we may be querying for soft-deleted resources | ||||||||||||||||||||||||||
const resource = await db.query.resource.findFirst({ | ||||||||||||||||||||||||||
where: and( | ||||||||||||||||||||||||||
eq(schema.resource.workspaceId, workspaceId), | ||||||||||||||||||||||||||
eq(schema.resource.identifier, identifier), | ||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (target == null) return false; | ||||||||||||||||||||||||||
if (resource == null) return false; | ||||||||||||||||||||||||||
return can | ||||||||||||||||||||||||||
.perform(Permission.ResourceGet) | ||||||||||||||||||||||||||
.on({ type: "resource", id: target.id }); | ||||||||||||||||||||||||||
.on({ type: "resource", id: resource.id }); | ||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
.handle<unknown, { params: { workspaceId: string; identifier: string } }>( | ||||||||||||||||||||||||||
async (_, { params }) => { | ||||||||||||||||||||||||||
// we don't check deletedAt as we may be querying for soft-deleted resources | ||||||||||||||||||||||||||
const data = await db.query.resource.findFirst({ | ||||||||||||||||||||||||||
where: and( | ||||||||||||||||||||||||||
eq(schema.resource.workspaceId, params.workspaceId), | ||||||||||||||||||||||||||
|
@@ -66,6 +69,7 @@ export const DELETE = request() | |||||||||||||||||||||||||
where: and( | ||||||||||||||||||||||||||
eq(schema.resource.workspaceId, workspaceId), | ||||||||||||||||||||||||||
eq(schema.resource.identifier, identifier), | ||||||||||||||||||||||||||
isNull(schema.resource.deletedAt), | ||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
@@ -77,21 +81,22 @@ export const DELETE = request() | |||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
.handle<unknown, { params: { workspaceId: string; identifier: string } }>( | ||||||||||||||||||||||||||
async (_, { params }) => { | ||||||||||||||||||||||||||
const target = await db.query.resource.findFirst({ | ||||||||||||||||||||||||||
const resource = await db.query.resource.findFirst({ | ||||||||||||||||||||||||||
where: and( | ||||||||||||||||||||||||||
eq(schema.resource.workspaceId, params.workspaceId), | ||||||||||||||||||||||||||
eq(schema.resource.identifier, params.identifier), | ||||||||||||||||||||||||||
isNull(schema.resource.deletedAt), | ||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (target == null) { | ||||||||||||||||||||||||||
if (resource == null) { | ||||||||||||||||||||||||||
return NextResponse.json( | ||||||||||||||||||||||||||
{ error: `Target not found for identifier: ${params.identifier}` }, | ||||||||||||||||||||||||||
{ status: 404 }, | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Comment on lines
+92
to
97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Maintain consistent terminology in error messages. The error message still refers to "Target" instead of "Resource". if (resource == null) {
return NextResponse.json(
- { error: `Target not found for identifier: ${params.identifier}` },
+ { error: `Resource not found for identifier: ${params.identifier}` },
{ status: 404 },
);
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
await db.delete(schema.resource).where(eq(schema.resource.id, target.id)); | ||||||||||||||||||||||||||
await deleteResources(db, [resource]); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return NextResponse.json({ success: true }); | ||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Soft delete check should be added for deployments
Based on the code analysis, deployments do support soft delete functionality, as evidenced by:
isNull(resource.deletedAt)
in multiple queriesAdd the suggested check to prevent relationships with soft-deleted deployments:
where( and( eq(SCHEMA.deployment.id, body.deploymentId), eq(SCHEMA.system.workspaceId, body.workspaceId), + isNull(SCHEMA.deployment.deletedAt), ), )
🔗 Analysis chain
Verify soft delete implementation for deployments
While the resource query correctly filters out soft-deleted resources, the deployment query doesn't have a similar check. This could potentially allow creating relationships with soft-deleted deployments.
Let's verify if deployments also support soft delete:
If deployments support soft delete, consider adding the check:
where( and( eq(SCHEMA.deployment.id, body.deploymentId), eq(SCHEMA.system.workspaceId, body.workspaceId), + isNull(SCHEMA.deployment.deletedAt), ), )
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 151
Script:
Length of output: 570
Script:
Length of output: 22994