Skip to content

Commit 3c7fbad

Browse files
committed
fix: invalidate Redis cache on link/pixel update + delete
Address Greptile review feedback on umami-software#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 872d737 commit 3c7fbad

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 type { QueryFilters } from '@/lib/types';
45

56
export async function findLink(criteria: Prisma.LinkFindUniqueArgs) {
@@ -59,9 +60,25 @@ export async function createLink(data: Prisma.LinkUncheckedCreateInput) {
5960
}
6061

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

6578
export async function deleteLink(linkId: string) {
66-
return prisma.client.link.delete({ where: { id: linkId } });
79+
const link = await prisma.client.link.delete({ where: { id: linkId } });
80+
if (redis.enabled) {
81+
await redis.client.del(`link:${link.slug}`);
82+
}
83+
return link;
6784
}

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 type { QueryFilters } from '@/lib/types';
45

56
export async function findPixel(criteria: Prisma.PixelFindUniqueArgs) {
@@ -54,9 +55,25 @@ export async function createPixel(data: Prisma.PixelUncheckedCreateInput) {
5455
}
5556

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

6073
export async function deletePixel(pixelId: string) {
61-
return prisma.client.pixel.delete({ where: { id: pixelId } });
74+
const pixel = await prisma.client.pixel.delete({ where: { id: pixelId } });
75+
if (redis.enabled) {
76+
await redis.client.del(`pixel:${pixel.slug}`);
77+
}
78+
return pixel;
6279
}

src/queries/prisma/user.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,15 @@ export async function deleteUser(userId: string) {
180180
}),
181181
client.share.deleteMany({ where: { entityId: { in: entityIds } } }),
182182
// deletedAt: null avoids restamping rows that were already soft-deleted earlier.
183+
// Spread `ownedFilter` (which is `{ userId }` in cloud mode) for consistency
184+
// with everything else in the function.
183185
client.link.updateMany({
184186
data: { deletedAt: new Date() },
185-
where: { userId, deletedAt: null },
187+
where: { ...ownedFilter, deletedAt: null },
186188
}),
187189
client.pixel.updateMany({
188190
data: { deletedAt: new Date() },
189-
where: { userId, deletedAt: null },
191+
where: { ...ownedFilter, deletedAt: null },
190192
}),
191193
client.board.deleteMany({ where: { userId } }),
192194
]).then(async result => {

0 commit comments

Comments
 (0)