anti db delete
This commit is contained in:
+102
-23
@@ -60,6 +60,13 @@ function isUserStarred(userId: number, ratingId: number, categoryId: number) {
|
||||
|
||||
let cacheLoaded = false;
|
||||
|
||||
const pendingUserVoteChanges = new Map<string, { userId: number; ratingId: number; ratingCategoryId: number; score: number }>();
|
||||
const pendingUserVoteDeletes = new Set<string>();
|
||||
const pendingAnonVoteChanges = new Map<string, { ip: string; ratingId: number; ratingCategoryId: number; score: number }>();
|
||||
const pendingAnonVoteDeletes = new Set<string>();
|
||||
const pendingStarChanges = new Map<string, { userId: number; ratingId: number; ratingCategoryId: number }>();
|
||||
const pendingStarDeletes = new Set<string>();
|
||||
|
||||
function getUserVotesForRating(userId: number, ratingId: number): Record<number, number> {
|
||||
const result: Record<number, number> = {};
|
||||
const perRating = cache.userVotes.get(userId)?.get(ratingId);
|
||||
@@ -152,32 +159,86 @@ async function rebuildCache() {
|
||||
|
||||
async function flushPendingOps() {
|
||||
if (!cacheLoaded) return;
|
||||
const userRatingData: { userId: number; ratingId: number; ratingCategoryId: number; score: number }[] = [];
|
||||
for (const [userId, perRating] of cache.userVotes) {
|
||||
for (const [ratingId, perCat] of perRating) {
|
||||
for (const [catId, score] of perCat) userRatingData.push({ userId, ratingId, ratingCategoryId: catId, score });
|
||||
}
|
||||
}
|
||||
const anonVoteData: { ip: string; ratingId: number; ratingCategoryId: number; score: number }[] = [];
|
||||
for (const [ip, perRating] of cache.anonVotes) {
|
||||
for (const [ratingId, perCat] of perRating) {
|
||||
for (const [catId, score] of perCat) anonVoteData.push({ ip, ratingId, ratingCategoryId: catId, score });
|
||||
}
|
||||
}
|
||||
const starData: { userId: number; ratingId: number; ratingCategoryId: number }[] = [];
|
||||
for (const [userId, perRating] of cache.userStars) {
|
||||
for (const [ratingId, cats] of perRating) {
|
||||
for (const catId of cats) starData.push({ userId, ratingId, ratingCategoryId: catId });
|
||||
}
|
||||
|
||||
const uvChanges = new Map(pendingUserVoteChanges);
|
||||
const uvDeletes = new Set(pendingUserVoteDeletes);
|
||||
const avChanges = new Map(pendingAnonVoteChanges);
|
||||
const avDeletes = new Set(pendingAnonVoteDeletes);
|
||||
const stChanges = new Map(pendingStarChanges);
|
||||
const stDeletes = new Set(pendingStarDeletes);
|
||||
|
||||
pendingUserVoteChanges.clear();
|
||||
pendingUserVoteDeletes.clear();
|
||||
pendingAnonVoteChanges.clear();
|
||||
pendingAnonVoteDeletes.clear();
|
||||
pendingStarChanges.clear();
|
||||
pendingStarDeletes.clear();
|
||||
|
||||
if (uvChanges.size === 0 && uvDeletes.size === 0 &&
|
||||
avChanges.size === 0 && avDeletes.size === 0 &&
|
||||
stChanges.size === 0 && stDeletes.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.userRating.deleteMany();
|
||||
await tx.anonymousRatingVote.deleteMany();
|
||||
await tx.ratingStar.deleteMany();
|
||||
if (userRatingData.length) await tx.userRating.createMany({ data: userRatingData });
|
||||
if (anonVoteData.length) await tx.anonymousRatingVote.createMany({ data: anonVoteData });
|
||||
if (starData.length) await tx.ratingStar.createMany({ data: starData });
|
||||
for (const [, v] of uvChanges) {
|
||||
await tx.userRating.upsert({
|
||||
where: { userId_ratingId_ratingCategoryId: { userId: v.userId, ratingId: v.ratingId, ratingCategoryId: v.ratingCategoryId } },
|
||||
create: { userId: v.userId, ratingId: v.ratingId, ratingCategoryId: v.ratingCategoryId, score: v.score },
|
||||
update: { score: v.score },
|
||||
});
|
||||
}
|
||||
for (const key of uvDeletes) {
|
||||
const [userId, ratingId, ratingCategoryId] = key.split(":").map(Number);
|
||||
await tx.userRating.deleteMany({ where: { userId, ratingId, ratingCategoryId } });
|
||||
}
|
||||
for (const [, v] of avChanges) {
|
||||
await tx.anonymousRatingVote.upsert({
|
||||
where: { ip_ratingId_ratingCategoryId: { ip: v.ip, ratingId: v.ratingId, ratingCategoryId: v.ratingCategoryId } },
|
||||
create: { ip: v.ip, ratingId: v.ratingId, ratingCategoryId: v.ratingCategoryId, score: v.score },
|
||||
update: { score: v.score },
|
||||
});
|
||||
}
|
||||
for (const key of avDeletes) {
|
||||
const [ip, ratingId, ratingCategoryId] = key.split(":");
|
||||
await tx.anonymousRatingVote.deleteMany({ where: { ip, ratingId: Number(ratingId), ratingCategoryId: Number(ratingCategoryId) } });
|
||||
}
|
||||
for (const [, v] of stChanges) {
|
||||
await tx.ratingStar.upsert({
|
||||
where: { userId_ratingId_ratingCategoryId: { userId: v.userId, ratingId: v.ratingId, ratingCategoryId: v.ratingCategoryId } },
|
||||
create: { userId: v.userId, ratingId: v.ratingId, ratingCategoryId: v.ratingCategoryId },
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
for (const key of stDeletes) {
|
||||
const [userId, ratingId, ratingCategoryId] = key.split(":").map(Number);
|
||||
await tx.ratingStar.deleteMany({ where: { userId, ratingId, ratingCategoryId } });
|
||||
}
|
||||
});
|
||||
|
||||
const [allVotes, anonVotes, allStars] = await Promise.all([
|
||||
prisma.userRating.findMany(),
|
||||
prisma.anonymousRatingVote.findMany(),
|
||||
prisma.ratingStar.findMany(),
|
||||
]);
|
||||
cache.userVotes.clear();
|
||||
for (const v of allVotes) {
|
||||
let u = cache.userVotes.get(v.userId); if (!u) { u = new Map(); cache.userVotes.set(v.userId, u); }
|
||||
let r = u.get(v.ratingId); if (!r) { r = new Map(); u.set(v.ratingId, r); }
|
||||
r.set(v.ratingCategoryId, v.score);
|
||||
}
|
||||
cache.anonVotes.clear();
|
||||
for (const v of anonVotes) {
|
||||
let a = cache.anonVotes.get(v.ip); if (!a) { a = new Map(); cache.anonVotes.set(v.ip, a); }
|
||||
let r = a.get(v.ratingId); if (!r) { r = new Map(); a.set(v.ratingId, r); }
|
||||
r.set(v.ratingCategoryId, v.score);
|
||||
}
|
||||
cache.userStars.clear();
|
||||
for (const s of allStars) {
|
||||
let u = cache.userStars.get(s.userId); if (!u) { u = new Map(); cache.userStars.set(s.userId, u); }
|
||||
let r = u.get(s.ratingId); if (!r) { r = new Set(); u.set(s.ratingId, r); }
|
||||
r.add(s.ratingCategoryId);
|
||||
}
|
||||
}
|
||||
|
||||
const uploadsDir = path.join(__dirname, "../uploads");
|
||||
@@ -509,12 +570,18 @@ app.post("/api/star", async (req, res) => {
|
||||
if (!cats) { cats = new Set(); perUser.set(ratingId, cats); }
|
||||
if (cats.has(catId)) {
|
||||
cats.delete(catId);
|
||||
const key = `${userId}:${ratingId}:${catId}`;
|
||||
pendingStarDeletes.add(key);
|
||||
pendingStarChanges.delete(key);
|
||||
res.json({ starred: false });
|
||||
} else {
|
||||
let catCount = 0;
|
||||
for (const sci of perUser.values()) { if (sci.has(catId)) catCount++; }
|
||||
if (catCount >= 3) { res.status(400).json({ error: "Max 3 stars per category" }); return; }
|
||||
cats.add(catId);
|
||||
const key = `${userId}:${ratingId}:${catId}`;
|
||||
pendingStarChanges.set(key, { userId, ratingId, ratingCategoryId: catId });
|
||||
pendingStarDeletes.delete(key);
|
||||
res.json({ starred: true });
|
||||
}
|
||||
});
|
||||
@@ -547,11 +614,17 @@ app.post("/api/vote", async (req, res) => {
|
||||
let u = cache.userVotes.get(userId); if (!u) { u = new Map(); cache.userVotes.set(userId, u); }
|
||||
let r = u.get(ratingId); if (!r) { r = new Map(); u.set(ratingId, r); }
|
||||
r.set(ratingCategoryId, score);
|
||||
const key = `${userId}:${ratingId}:${ratingCategoryId}`;
|
||||
pendingUserVoteChanges.set(key, { userId, ratingId, ratingCategoryId, score });
|
||||
pendingUserVoteDeletes.delete(key);
|
||||
} else {
|
||||
const ip = getIp(req);
|
||||
let a = cache.anonVotes.get(ip); if (!a) { a = new Map(); cache.anonVotes.set(ip, a); }
|
||||
let r = a.get(ratingId); if (!r) { r = new Map(); a.set(ratingId, r); }
|
||||
r.set(ratingCategoryId, score);
|
||||
const key = `${ip}:${ratingId}:${ratingCategoryId}`;
|
||||
pendingAnonVoteChanges.set(key, { ip, ratingId, ratingCategoryId, score });
|
||||
pendingAnonVoteDeletes.delete(key);
|
||||
}
|
||||
res.json(getAvgAndTotal(ratingId, ratingCategoryId));
|
||||
});
|
||||
@@ -566,9 +639,15 @@ app.post("/api/unvote", async (req, res) => {
|
||||
}
|
||||
if (userId) {
|
||||
cache.userVotes.get(userId)?.get(ratingId)?.delete(ratingCategoryId);
|
||||
const key = `${userId}:${ratingId}:${ratingCategoryId}`;
|
||||
pendingUserVoteDeletes.add(key);
|
||||
pendingUserVoteChanges.delete(key);
|
||||
} else {
|
||||
const ip = getIp(req);
|
||||
cache.anonVotes.get(ip)?.get(ratingId)?.delete(ratingCategoryId);
|
||||
const key = `${ip}:${ratingId}:${ratingCategoryId}`;
|
||||
pendingAnonVoteDeletes.add(key);
|
||||
pendingAnonVoteChanges.delete(key);
|
||||
}
|
||||
res.json(getAvgAndTotal(ratingId, ratingCategoryId));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user