Skip to content

Commit

Permalink
fix: Soft delete resources (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
adityachoudhari26 authored Nov 21, 2024
1 parent d3ca4da commit 87eaa9a
Show file tree
Hide file tree
Showing 34 changed files with 4,606 additions and 124 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { and, eq, notInArray } from "@ctrlplane/db";
import { and, eq, isNull, notInArray } from "@ctrlplane/db";
import { db } from "@ctrlplane/db/client";
import {
deployment,
Expand Down Expand Up @@ -40,6 +40,7 @@ export const GET = async (
"completed",
"invalid_job_agent",
]),
isNull(resource.deletedAt),
),
)
.then((rows) =>
Expand Down
4 changes: 2 additions & 2 deletions apps/webservice/src/app/api/v1/jobs/[jobId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { and, eq, takeFirstOrNull } from "@ctrlplane/db";
import { and, eq, isNull, takeFirstOrNull } from "@ctrlplane/db";
import { db } from "@ctrlplane/db/client";
import {
deployment,
Expand Down Expand Up @@ -83,7 +83,7 @@ export const GET = request()
.leftJoin(resource, eq(resource.id, releaseJobTrigger.resourceId))
.leftJoin(release, eq(release.id, releaseJobTrigger.releaseId))
.leftJoin(deployment, eq(deployment.id, release.deploymentId))
.where(eq(job.id, params.jobId))
.where(and(eq(job.id, params.jobId), isNull(resource.deletedAt)))
.then(takeFirstOrNull);

if (row == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";

import { and, eq, takeFirstOrNull } from "@ctrlplane/db";
import { and, eq, isNull, takeFirstOrNull } from "@ctrlplane/db";
import * as SCHEMA from "@ctrlplane/db/schema";

import { authn } from "../../auth";
Expand All @@ -27,6 +27,7 @@ export const POST = request()
and(
eq(SCHEMA.resource.identifier, body.resourceIdentifier),
eq(SCHEMA.resource.workspaceId, body.workspaceId),
isNull(SCHEMA.resource.deletedAt),
),
)
.then(takeFirstOrNull);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";

import { and, eq, takeFirstOrNull } from "@ctrlplane/db";
import { and, eq, isNull, takeFirstOrNull } from "@ctrlplane/db";
import * as SCHEMA from "@ctrlplane/db/schema";

import { authn } from "../../auth";
Expand Down Expand Up @@ -28,6 +28,7 @@ export const POST = request()
and(
eq(SCHEMA.resource.identifier, body.fromIdentifier),
eq(SCHEMA.resource.workspaceId, body.workspaceId),
isNull(SCHEMA.resource.deletedAt),
),
)
.then(takeFirstOrNull);
Expand All @@ -44,6 +45,7 @@ export const POST = request()
and(
eq(SCHEMA.resource.identifier, body.toIdentifier),
eq(SCHEMA.resource.workspaceId, body.workspaceId),
isNull(SCHEMA.resource.deletedAt),
),
)
.then(takeFirstOrNull);
Expand Down
30 changes: 13 additions & 17 deletions apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { NextResponse } from "next/server";
import _ from "lodash";
import { z } from "zod";

import { eq } from "@ctrlplane/db";
import { and, eq, isNull } from "@ctrlplane/db";
import * as schema from "@ctrlplane/db/schema";
import { upsertResources } from "@ctrlplane/job-dispatch";
import { deleteResources, upsertResources } from "@ctrlplane/job-dispatch";
import { variablesAES256 } from "@ctrlplane/secrets";
import { Permission } from "@ctrlplane/validators/auth";

Expand All @@ -22,13 +22,10 @@ export const GET = request()
}),
)
.handle(async ({ db }, { params }: { params: { resourceId: string } }) => {
// we don't check deletedAt as we may be querying for soft-deleted resources
const data = await db.query.resource.findFirst({
where: eq(schema.resource.id, params.resourceId),
with: {
metadata: true,
variables: true,
provider: true,
},
with: { metadata: true, variables: true, provider: true },
});

if (data == null)
Expand Down Expand Up @@ -85,9 +82,10 @@ export const PATCH = request()
{ body: z.infer<typeof patchSchema> },
{ params: { resourceId: string } }
>(async ({ db, body }, { params }) => {
const resource = await db.query.resource.findFirst({
where: eq(schema.resource.id, params.resourceId),
});
const isResource = eq(schema.resource.id, params.resourceId);
const isNotDeleted = isNull(schema.resource.deletedAt);
const where = and(isResource, isNotDeleted);
const resource = await db.query.resource.findFirst({ where });

if (resource == null)
return NextResponse.json(
Expand All @@ -110,19 +108,17 @@ export const DELETE = request()
),
)
.handle(async ({ db }, { params }: { params: { resourceId: string } }) => {
const resource = await db.query.resource.findFirst({
where: eq(schema.resource.id, params.resourceId),
});
const isResource = eq(schema.resource.id, params.resourceId);
const isNotDeleted = isNull(schema.resource.deletedAt);
const where = and(isResource, isNotDeleted);
const resource = await db.query.resource.findFirst({ where });

if (resource == null)
return NextResponse.json(
{ error: "Resource not found" },
{ status: 404 },
);

await db
.delete(schema.resource)
.where(eq(schema.resource.id, params.resourceId));

await deleteResources(db, [resource]);
return NextResponse.json({ success: true });
});
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";
Expand All @@ -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),
Expand Down Expand Up @@ -66,6 +69,7 @@ export const DELETE = request()
where: and(
eq(schema.resource.workspaceId, workspaceId),
eq(schema.resource.identifier, identifier),
isNull(schema.resource.deletedAt),
),
});

Expand All @@ -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 },
);
}

await db.delete(schema.resource).where(eq(schema.resource.id, target.id));
await deleteResources(db, [resource]);

return NextResponse.json({ success: true });
},
Expand Down
14 changes: 11 additions & 3 deletions packages/api/src/router/deployment-variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
asc,
eq,
isNotNull,
isNull,
ne,
sql,
takeFirst,
Expand Down Expand Up @@ -214,11 +215,17 @@ const valueRouter = createTRPCRouter({
};

const oldTargets = await ctx.db.query.resource.findMany({
where: resourceMatchesMetadata(ctx.db, oldTargetFilter),
where: and(
resourceMatchesMetadata(ctx.db, oldTargetFilter),
isNull(resource.deletedAt),
),
});

const newTargets = await ctx.db.query.resource.findMany({
where: resourceMatchesMetadata(ctx.db, newTargetFilter),
where: and(
resourceMatchesMetadata(ctx.db, newTargetFilter),
isNull(resource.deletedAt),
),
});

const oldTargetIds = new Set(oldTargets.map((t) => t.id));
Expand Down Expand Up @@ -299,7 +306,6 @@ export const deploymentVariableRouter = createTRPCRouter({
const deploymentVariables = await ctx.db
.select()
.from(resource)
.where(eq(resource.id, input))
.innerJoin(system, eq(resource.workspaceId, system.workspaceId))
.innerJoin(deployment, eq(deployment.systemId, system.id))
.innerJoin(
Expand All @@ -310,6 +316,7 @@ export const deploymentVariableRouter = createTRPCRouter({
deploymentVariableValue,
eq(deploymentVariableValue.variableId, deploymentVariable.id),
)
.where(and(eq(resource.id, input), isNull(resource.deletedAt)))
.then((rows) =>
_.chain(rows)
.groupBy((r) => r.deployment_variable.id)
Expand All @@ -331,6 +338,7 @@ export const deploymentVariableRouter = createTRPCRouter({
.where(
and(
eq(resource.id, input),
isNull(resource.deletedAt),
resourceMatchesMetadata(ctx.db, targetFilter),
),
)
Expand Down
5 changes: 4 additions & 1 deletion packages/api/src/router/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
eq,
inArray,
isNotNull,
isNull,
sql,
takeFirst,
takeFirstOrNull,
Expand Down Expand Up @@ -417,6 +418,7 @@ export const deploymentRouter = createTRPCRouter({
and(
eq(release.deploymentId, input),
eq(latestJobsPerResource.rank, 1),
isNull(resource.deletedAt),
),
)
.then((r) =>
Expand Down Expand Up @@ -620,7 +622,7 @@ export const deploymentRouter = createTRPCRouter({
const tg = await ctx.db
.select()
.from(resource)
.where(eq(resource.id, input))
.where(and(eq(resource.id, input), isNull(resource.deletedAt)))
.then(takeFirst);

const envs = await ctx.db
Expand Down Expand Up @@ -659,6 +661,7 @@ export const deploymentRouter = createTRPCRouter({
.where(
and(
eq(resource.id, input),
isNull(resource.deletedAt),
inArray(job.status, [
JobStatus.Completed,
JobStatus.Pending,
Expand Down
15 changes: 11 additions & 4 deletions packages/api/src/router/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
eq,
inArray,
isNotNull,
isNull,
ne,
not,
takeFirst,
Expand Down Expand Up @@ -170,9 +171,12 @@ export const environmentRouter = createTRPCRouter({
.select()
.from(resource)
.where(
resourceMatchesMetadata(
ctx.db,
e.environment.resourceFilter,
and(
isNull(resource.deletedAt),
resourceMatchesMetadata(
ctx.db,
e.environment.resourceFilter,
),
),
)
: [],
Expand Down Expand Up @@ -269,6 +273,7 @@ export const environmentRouter = createTRPCRouter({
.where(
and(
eq(resource.workspaceId, oldEnv.system.workspaceId),
isNull(resource.deletedAt),
newQuery,
oldQuery && not(oldQuery),
),
Expand All @@ -277,6 +282,7 @@ export const environmentRouter = createTRPCRouter({
const removedResources = await ctx.db.query.resource.findMany({
where: and(
eq(resource.workspaceId, oldEnv.system.workspaceId),
isNull(resource.deletedAt),
oldQuery,
newQuery && not(newQuery),
),
Expand All @@ -299,10 +305,11 @@ export const environmentRouter = createTRPCRouter({
otherEnvFilters.length > 0
? resourceMatchesMetadata(ctx.db, sysFilter)
: undefined;
const isNotDeleted = isNull(resource.deletedAt);

const removedFromSystemResources =
await ctx.db.query.resource.findMany({
where: and(isRemovedFromEnv, isRemovedFromSystem),
where: and(isRemovedFromEnv, isRemovedFromSystem, isNotDeleted),
});

const events = removedFromSystemResources.flatMap((resource) =>
Expand Down
Loading

0 comments on commit 87eaa9a

Please sign in to comment.