fix: revoke points when a comment is deleted
All checks were successful
Build and Push Backend Image / build (push) Successful in 43s
All checks were successful
Build and Push Backend Image / build (push) Successful in 43s
- revokePoints now accepts optional recordingId; when absent it deletes one matching row (for actions like COMMENT_CREATE that have no recording) - deleteComment queues revokePoints + checkAchievements so leaderboard and social achievements stay in sync Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,18 @@ builder.mutationField("deleteComment", (t) =>
|
|||||||
if (!comment[0]) throw new GraphQLError("Comment not found");
|
if (!comment[0]) throw new GraphQLError("Comment not found");
|
||||||
requireOwnerOrAdmin(ctx, comment[0].user_id);
|
requireOwnerOrAdmin(ctx, comment[0].user_id);
|
||||||
await ctx.db.delete(comments).where(eq(comments.id, args.id));
|
await ctx.db.delete(comments).where(eq(comments.id, args.id));
|
||||||
|
|
||||||
|
await gamificationQueue.add("revokePoints", {
|
||||||
|
job: "revokePoints",
|
||||||
|
userId: comment[0].user_id,
|
||||||
|
action: "COMMENT_CREATE",
|
||||||
|
});
|
||||||
|
await gamificationQueue.add("checkAchievements", {
|
||||||
|
job: "checkAchievements",
|
||||||
|
userId: comment[0].user_id,
|
||||||
|
category: "social",
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { eq, sql, and, gt, isNotNull, count, sum } from "drizzle-orm";
|
import { eq, sql, and, gt, isNull, isNotNull, count, sum } from "drizzle-orm";
|
||||||
import type { DB } from "../db/connection";
|
import type { DB } from "../db/connection";
|
||||||
import {
|
import {
|
||||||
user_points,
|
user_points,
|
||||||
@@ -45,17 +45,33 @@ export async function revokePoints(
|
|||||||
db: DB,
|
db: DB,
|
||||||
userId: string,
|
userId: string,
|
||||||
action: keyof typeof POINT_VALUES,
|
action: keyof typeof POINT_VALUES,
|
||||||
recordingId: string,
|
recordingId?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await db
|
const recordingCondition = recordingId
|
||||||
.delete(user_points)
|
? eq(user_points.recording_id, recordingId)
|
||||||
.where(
|
: isNull(user_points.recording_id);
|
||||||
and(
|
|
||||||
eq(user_points.user_id, userId),
|
// When no recordingId (e.g. COMMENT_CREATE), delete only one row so each
|
||||||
eq(user_points.action, action),
|
// revoke undoes exactly one prior award.
|
||||||
eq(user_points.recording_id, recordingId),
|
if (!recordingId) {
|
||||||
),
|
const row = await db
|
||||||
);
|
.select({ id: user_points.id })
|
||||||
|
.from(user_points)
|
||||||
|
.where(
|
||||||
|
and(eq(user_points.user_id, userId), eq(user_points.action, action), recordingCondition),
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
if (row[0]) {
|
||||||
|
await db.delete(user_points).where(eq(user_points.id, row[0].id));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await db
|
||||||
|
.delete(user_points)
|
||||||
|
.where(
|
||||||
|
and(eq(user_points.user_id, userId), eq(user_points.action, action), recordingCondition),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await updateUserStats(db, userId);
|
await updateUserStats(db, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const log = logger.child({ component: "gamification-worker" });
|
|||||||
|
|
||||||
export type GamificationJobData =
|
export type GamificationJobData =
|
||||||
| { job: "awardPoints"; userId: string; action: keyof typeof POINT_VALUES; recordingId?: string }
|
| { job: "awardPoints"; userId: string; action: keyof typeof POINT_VALUES; recordingId?: string }
|
||||||
| { job: "revokePoints"; userId: string; action: keyof typeof POINT_VALUES; recordingId: string }
|
| { job: "revokePoints"; userId: string; action: keyof typeof POINT_VALUES; recordingId?: string }
|
||||||
| { job: "checkAchievements"; userId: string; category?: string };
|
| { job: "checkAchievements"; userId: string; category?: string };
|
||||||
|
|
||||||
export function startGamificationWorker(): Worker {
|
export function startGamificationWorker(): Worker {
|
||||||
|
|||||||
Reference in New Issue
Block a user