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, }