Skip to content

Commit c72d60f

Browse files
committed
fix: invalidate Redis cache on link/pixel update + delete
Address Greptile review feedback on #4245. - deleteLink and deletePixel now redis.client.del('link:slug' / 'pixel:slug') using the slug returned by Prisma's delete(). Previously the row was hard- deleted but the Redis cache (24h TTL) kept serving the slug, so /q/<slug> and /p/<slug> kept firing for up to a day after deletion. - updateLink and updatePixel now invalidate the cache for the current slug, and additionally for the previous slug if the slug was changed. Previously changing a link's destination URL or slug left the public cache stale. - Cloud-mode link.updateMany and pixel.updateMany in deleteUser now spread ownedFilter (which is { userId } in cloud mode) instead of hardcoding { userId }, so the cleanup intent stays consistent if ownedFilter ever evolves. Verified empirically against a Docker Postgres + Redis: deleted link's /q/<slug> returns 404 immediately (was: still redirected to old URL for 24h); slug rename invalidates both old and new cache keys.
1 parent 605a965 commit c72d60f

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

src/queries/prisma/link.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Prisma } from '@/generated/prisma/client';
22
import prisma from '@/lib/prisma';
3+
import redis from '@/lib/redis';
34
import { sanitizeSortFilters } from '@/lib/sort';
45
import type { QueryFilters } from '@/lib/types';
56

@@ -63,9 +64,25 @@ export async function createLink(data: Prisma.LinkUncheckedCreateInput) {
6364
}
6465

6566
export async function updateLink(linkId: string, data: any) {
66-
return prisma.client.link.update({ where: { id: linkId }, data });
67+
// Fetch the old slug so we can invalidate its cache entry if the slug changes.
68+
const previous = await prisma.client.link.findUnique({
69+
where: { id: linkId },
70+
select: { slug: true },
71+
});
72+
const link = await prisma.client.link.update({ where: { id: linkId }, data });
73+
if (redis.enabled) {
74+
await redis.client.del(`link:${link.slug}`);
75+
if (previous && previous.slug !== link.slug) {
76+
await redis.client.del(`link:${previous.slug}`);
77+
}
78+
}
79+
return link;
6780
}
6881

6982
export async function deleteLink(linkId: string) {
70-
return prisma.client.link.delete({ where: { id: linkId } });
83+
const link = await prisma.client.link.delete({ where: { id: linkId } });
84+
if (redis.enabled) {
85+
await redis.client.del(`link:${link.slug}`);
86+
}
87+
return link;
7188
}

src/queries/prisma/pixel.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Prisma } from '@/generated/prisma/client';
22
import prisma from '@/lib/prisma';
3+
import redis from '@/lib/redis';
34
import { sanitizeSortFilters } from '@/lib/sort';
45
import type { QueryFilters } from '@/lib/types';
56

@@ -58,9 +59,25 @@ export async function createPixel(data: Prisma.PixelUncheckedCreateInput) {
5859
}
5960

6061
export async function updatePixel(pixelId: string, data: any) {
61-
return prisma.client.pixel.update({ where: { id: pixelId }, data });
62+
// Fetch the old slug so we can invalidate its cache entry if the slug changes.
63+
const previous = await prisma.client.pixel.findUnique({
64+
where: { id: pixelId },
65+
select: { slug: true },
66+
});
67+
const pixel = await prisma.client.pixel.update({ where: { id: pixelId }, data });
68+
if (redis.enabled) {
69+
await redis.client.del(`pixel:${pixel.slug}`);
70+
if (previous && previous.slug !== pixel.slug) {
71+
await redis.client.del(`pixel:${previous.slug}`);
72+
}
73+
}
74+
return pixel;
6275
}
6376

6477
export async function deletePixel(pixelId: string) {
65-
return prisma.client.pixel.delete({ where: { id: pixelId } });
78+
const pixel = await prisma.client.pixel.delete({ where: { id: pixelId } });
79+
if (redis.enabled) {
80+
await redis.client.del(`pixel:${pixel.slug}`);
81+
}
82+
return pixel;
6683
}

src/queries/prisma/user.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,15 @@ export async function deleteUser(userId: string) {
183183
}),
184184
client.share.deleteMany({ where: { entityId: { in: entityIds } } }),
185185
// deletedAt: null avoids restamping rows that were already soft-deleted earlier.
186+
// Spread `ownedFilter` (which is `{ userId }` in cloud mode) for consistency
187+
// with everything else in the function.
186188
client.link.updateMany({
187189
data: { deletedAt: new Date() },
188-
where: { userId, deletedAt: null },
190+
where: { ...ownedFilter, deletedAt: null },
189191
}),
190192
client.pixel.updateMany({
191193
data: { deletedAt: new Date() },
192-
where: { userId, deletedAt: null },
194+
where: { ...ownedFilter, deletedAt: null },
193195
}),
194196
client.board.deleteMany({ where: { userId } }),
195197
]).then(async result => {

0 commit comments

Comments
 (0)