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 }