HotTRDealsBackend/services/redis/hotDealList.service.js
2026-02-04 06:39:10 +00:00

206 lines
6.4 KiB
JavaScript

const { getRedisClient } = require("./client")
const { getSellersByIds, getSellerById } = require("./sellerCache.service")
const { getUsersPublicByIds, setUsersPublicInRedis } = require("./userPublicCache.service")
const userDB = require("../../db/user.db")
const { getMyVotesForDeals } = require("./dealVote.service")
function createRedisClient() {
return getRedisClient()
}
async function getHotDealListId(redis, hotListId) {
if (hotListId) return String(hotListId)
const latest = await redis.get("lists:hot:latest")
return latest ? String(latest) : null
}
async function getListId(redis, listKeyPrefix, listId) {
if (listId) return String(listId)
const latest = await redis.get(`${listKeyPrefix}:latest`)
return latest ? String(latest) : null
}
async function getHotDealIds({ hotListId } = {}) {
const redis = createRedisClient()
try {
const listId = await getHotDealListId(redis, hotListId)
if (!listId) return { hotListId: null, dealIds: [] }
const key = `lists:hot:${listId}`
const raw = await redis.call("JSON.GET", key, "$.dealIds")
if (!raw) return { hotListId: listId, dealIds: [] }
const parsed = JSON.parse(raw)
const dealIds = Array.isArray(parsed) ? parsed[0] : []
return {
hotListId: listId,
dealIds: Array.isArray(dealIds) ? dealIds.map((id) => Number(id)) : [],
}
} finally {}
}
async function getDealsByIdsFromRedis(ids = [], viewerId = null) {
if (!ids.length) return []
const redis = createRedisClient()
try {
const pipeline = redis.pipeline()
ids.forEach((id) => {
pipeline.call("JSON.GET", `data:deals:${id}`)
})
const results = await pipeline.exec()
const deals = []
results.forEach(([, raw], idx) => {
if (!raw) return
try {
const deal = JSON.parse(raw)
if (deal && deal.id) {
deals.push({ deal, index: idx })
}
} catch {
return
}
})
// Preserve original order (ids array order)
const ordered = deals
.sort((a, b) => a.index - b.index)
.map((item) => item.deal)
const sellerIds = ordered
.map((deal) => Number(deal?.sellerId))
.filter((id) => Number.isInteger(id) && id > 0)
const sellerMap = sellerIds.length ? await getSellersByIds(sellerIds) : new Map()
const voteMap = viewerId ? await getMyVotesForDeals(ordered.map((d) => d.id), viewerId) : new Map()
const userIds = ordered
.map((deal) => Number(deal?.userId))
.filter((id) => Number.isInteger(id) && id > 0)
const userMap = userIds.length ? await getUsersPublicByIds(userIds) : new Map()
const missingUserIds = Array.from(
new Set(userIds.filter((id) => !userMap.has(id)))
)
if (missingUserIds.length) {
const missingSet = new Set(missingUserIds)
const ttlPipeline = redis.pipeline()
ordered.forEach((deal) => {
ttlPipeline.ttl(`data:deals:${deal.id}`)
})
const ttlResults = await ttlPipeline.exec()
const ttlByDealId = new Map()
ttlResults.forEach(([, ttl], idx) => {
const dealId = ordered[idx]?.id
if (dealId) ttlByDealId.set(Number(dealId), Number(ttl))
})
const users = await userDB.findUsersByIds(missingUserIds, {
select: {
id: true,
username: true,
avatarUrl: true,
userBadges: {
orderBy: { earnedAt: "desc" },
select: {
earnedAt: true,
badge: { select: { id: true, name: true, iconUrl: true, description: true } },
},
},
},
})
const ttlByUserId = {}
ordered.forEach((deal) => {
const uid = Number(deal?.userId)
if (!missingSet.has(uid)) return
const ttl = ttlByDealId.get(Number(deal?.id))
if (ttl == null || ttl <= 0) return
ttlByUserId[uid] = Math.max(ttlByUserId[uid] || 0, ttl)
})
if (users.length) {
await setUsersPublicInRedis(users, { ttlSecondsById: ttlByUserId })
users.forEach((u) => userMap.set(u.id, u))
}
}
const enriched = ordered.map((deal) => {
let next = deal
if (!next?.user && next?.userId) {
const user = userMap.get(Number(next.userId)) || null
if (user) next = { ...next, user }
}
if (!next?.seller && next?.sellerId) {
const seller = sellerMap.get(Number(next.sellerId)) || null
if (seller) next = { ...next, seller }
}
const myVote = viewerId ? Number(voteMap.get(Number(next.id)) ?? 0) : 0
const isSaved = viewerId
? Array.isArray(next.savedBy) &&
next.savedBy.some((s) => Number(s?.userId) === Number(viewerId))
: false
return { ...next, myVote, isSaved }
})
return enriched
} finally {}
}
async function getDealByIdFromRedis(id, viewerId = null) {
const redis = createRedisClient()
try {
const raw = await redis.call("JSON.GET", `data:deals:${id}`)
if (!raw) return null
let deal = JSON.parse(raw)
if (deal?.sellerId && !deal?.seller) {
const seller = await getSellerById(Number(deal.sellerId))
if (seller) deal = { ...deal, seller }
}
if (viewerId) {
const voteMap = await getMyVotesForDeals([deal.id], viewerId)
const isSaved = Array.isArray(deal.savedBy)
? deal.savedBy.some((s) => Number(s?.userId) === Number(viewerId))
: false
deal = { ...deal, myVote: Number(voteMap.get(Number(deal.id)) ?? 0), isSaved }
}
return deal
} finally {}
}
async function getHotRangeDealIds({ range, listId } = {}) {
const redis = createRedisClient()
try {
const prefix =
range === "day"
? "lists:hot_day"
: range === "week"
? "lists:hot_week"
: range === "month"
? "lists:hot_month"
: null
if (!prefix) return { listId: null, dealIds: [] }
const resolvedId = await getListId(redis, prefix, listId)
if (!resolvedId) return { listId: null, dealIds: [] }
const key = `${prefix}:${resolvedId}`
const raw = await redis.call("JSON.GET", key, "$.dealIds")
if (!raw) return { listId: resolvedId, dealIds: [] }
const parsed = JSON.parse(raw)
const dealIds = Array.isArray(parsed) ? parsed[0] : []
return {
listId: resolvedId,
dealIds: Array.isArray(dealIds) ? dealIds.map((id) => Number(id)) : [],
}
} finally {}
}
module.exports = { getHotDealIds, getHotRangeDealIds, getDealsByIdsFromRedis, getDealByIdFromRedis }