HotTRDealsBackend/services/redis/dealVote.service.js
2026-02-07 22:42:02 +00:00

142 lines
4.6 KiB
JavaScript

const { getRedisClient } = require("./client")
const dealDb = require("../../db/deal.db")
const { ensureMinDealTtl } = require("./dealCache.service")
function createRedisClient() {
return getRedisClient()
}
const USER_VOTE_HASH_PREFIX = "users:votes:"
const USER_VOTE_TTL_SECONDS = 6 * 60 * 60
async function updateDealVoteInRedis({ dealId, userId, voteType, score }) {
if (!dealId || !userId) return
const redis = createRedisClient()
try {
const key = `deals:cache:${dealId}`
const raw = await redis.call("JSON.GET", key)
if (!raw) return { updated: false, delta: 0, score: null }
const deal = JSON.parse(raw)
const currentScore = Number.isFinite(deal?.score) ? Number(deal.score) : 0
const maxNotifiedMilestone = Number.isFinite(deal?.maxNotifiedMilestone)
? Number(deal.maxNotifiedMilestone)
: 0
const dealUserId = Number(deal?.userId)
const normalizedUserId = Number(userId)
const normalizedVoteType = Number(voteType)
const oldRaw = await redis.hget(
`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`,
String(dealId)
)
const oldVote = oldRaw == null ? 0 : Number(oldRaw)
const delta = normalizedVoteType - oldVote
const nextScore =
score !== undefined && score !== null ? Number(score) : currentScore + delta
await redis.call("JSON.SET", key, "$.score", nextScore)
if (normalizedVoteType === 0) {
await redis.hdel(`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`, String(dealId))
} else {
await redis.hset(`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`, String(dealId), String(normalizedVoteType))
await redis.expire(`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`, USER_VOTE_TTL_SECONDS)
}
await ensureMinDealTtl(dealId, { minSeconds: 15 * 60 })
return { updated: true, delta, score: nextScore, maxNotifiedMilestone, dealUserId }
} catch {
return { updated: false, delta: 0, score: null }
} finally {}
}
async function getDealVoteFromRedis(dealId, userId) {
const id = Number(dealId)
const uid = Number(userId)
if (!Number.isInteger(id) || !Number.isInteger(uid)) return 0
const redis = createRedisClient()
try {
const userKey = `${USER_VOTE_HASH_PREFIX}${uid}`
const exists = await redis.exists(userKey)
if (!exists) {
const dbVotes = await dealDb.findVotes(
{ userId: Number(uid) },
{ select: { dealId: true, voteType: true } }
)
const pipeline = redis.pipeline()
dbVotes.forEach((vote) => {
const did = Number(vote.dealId)
const v = Number(vote.voteType)
if (!Number.isInteger(did)) return
if (Number.isFinite(v) && v !== 0) {
pipeline.hset(userKey, String(did), String(v))
}
})
pipeline.expire(userKey, USER_VOTE_TTL_SECONDS)
await pipeline.exec()
const fromDb = dbVotes.find((v) => Number(v.dealId) === id)
const dbVote = Number(fromDb?.voteType ?? 0)
return Number.isFinite(dbVote) ? dbVote : 0
}
const raw = await redis.hget(userKey, String(id))
if (raw != null) {
const value = Number(raw)
return Number.isFinite(value) ? value : 0
}
return 0
} catch {
return 0
} finally {}
}
async function getMyVotesForDeals(dealIds = [], userId) {
const uid = Number(userId)
if (!Number.isInteger(uid)) return new Map()
const ids = (Array.isArray(dealIds) ? dealIds : [])
.map((id) => Number(id))
.filter((id) => Number.isInteger(id) && id > 0)
if (!ids.length) return new Map()
const redis = createRedisClient()
try {
const userKey = `${USER_VOTE_HASH_PREFIX}${uid}`
const exists = await redis.exists(userKey)
if (exists) {
const results = await redis.hmget(userKey, ids.map(String))
const map = new Map()
results.forEach((raw, idx) => {
const value = raw == null ? 0 : Number(raw)
map.set(ids[idx], Number.isFinite(value) ? value : 0)
})
return map
}
const dbVotes = await dealDb.findVotes(
{ dealId: { in: ids }, userId: Number(uid) },
{ select: { dealId: true, voteType: true } }
)
const map = new Map()
ids.forEach((id) => map.set(id, 0))
const pipeline = redis.pipeline()
dbVotes.forEach((vote) => {
const did = Number(vote.dealId)
const v = Number(vote.voteType)
if (!Number.isInteger(did)) return
if (Number.isFinite(v) && v !== 0) {
map.set(did, v)
pipeline.hset(userKey, String(did), String(v))
}
})
pipeline.expire(userKey, USER_VOTE_TTL_SECONDS)
await pipeline.exec()
return map
} catch {
return new Map()
} finally {}
}
module.exports = {
updateDealVoteInRedis,
getDealVoteFromRedis,
getMyVotesForDeals,
}