142 lines
4.6 KiB
JavaScript
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,
|
|
}
|