HotTRDealsBackend/services/redis/sellerDealIndex.service.js
2026-02-09 21:47:55 +00:00

158 lines
4.6 KiB
JavaScript

const { getRedisClient } = require("./client")
const SELLER_DEAL_INDEX_KEY_PREFIX = "index:seller:"
const SELLER_DEAL_INDEX_KEY_SUFFIX = ":deals"
function normalizePositiveInt(value) {
const num = Number(value)
if (!Number.isInteger(num) || num <= 0) return null
return num
}
function normalizeEpochMs(value) {
const num = Number(value)
if (!Number.isFinite(num) || num <= 0) return null
return Math.floor(num)
}
function toEpochMs(value) {
if (value instanceof Date) return value.getTime()
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
return date.getTime()
}
function isActiveStatus(status) {
return String(status || "").toUpperCase() === "ACTIVE"
}
function getSellerDealIndexKey(sellerId) {
const sid = normalizePositiveInt(sellerId)
if (!sid) return null
return `${SELLER_DEAL_INDEX_KEY_PREFIX}${sid}${SELLER_DEAL_INDEX_KEY_SUFFIX}`
}
function normalizeDealIndexPayload(payload = {}) {
const dealId = normalizePositiveInt(payload.dealId ?? payload.id)
const sellerId = normalizePositiveInt(payload.sellerId)
const createdAtTs =
normalizeEpochMs(payload.createdAtTs) ?? normalizeEpochMs(toEpochMs(payload.createdAt))
const status = String(payload.status || "").toUpperCase()
return { dealId, sellerId, createdAtTs, status }
}
function isIndexableDeal(payload = {}) {
const normalized = normalizeDealIndexPayload(payload)
return Boolean(
normalized.dealId &&
normalized.sellerId &&
normalized.createdAtTs &&
isActiveStatus(normalized.status)
)
}
function addDealToSellerIndexInPipeline(pipeline, payload = {}) {
const normalized = normalizeDealIndexPayload(payload)
if (!normalized.dealId || !normalized.sellerId || !normalized.createdAtTs) return false
if (!isActiveStatus(normalized.status)) return false
const key = getSellerDealIndexKey(normalized.sellerId)
if (!key) return false
pipeline.zadd(key, String(normalized.createdAtTs), String(normalized.dealId))
return true
}
function removeDealFromSellerIndexInPipeline(pipeline, payload = {}) {
const normalized = normalizeDealIndexPayload(payload)
if (!normalized.dealId || !normalized.sellerId) return false
const key = getSellerDealIndexKey(normalized.sellerId)
if (!key) return false
pipeline.zrem(key, String(normalized.dealId))
return true
}
async function reconcileDealSellerIndex({ before = null, after = null } = {}) {
const prev = normalizeDealIndexPayload(before || {})
const next = normalizeDealIndexPayload(after || {})
const prevIndexable = isIndexableDeal(prev)
const nextIndexable = isIndexableDeal(next)
const redis = getRedisClient()
const pipeline = redis.pipeline()
let commandCount = 0
if (prevIndexable) {
const removedForStatus = !nextIndexable
const movedSeller = nextIndexable && prev.sellerId !== next.sellerId
if (removedForStatus || movedSeller) {
if (removeDealFromSellerIndexInPipeline(pipeline, prev)) commandCount += 1
}
}
if (nextIndexable) {
const isNew = !prevIndexable
const movedSeller = prevIndexable && prev.sellerId !== next.sellerId
const scoreChanged =
prevIndexable &&
prev.sellerId === next.sellerId &&
Number(prev.createdAtTs) !== Number(next.createdAtTs)
if (isNew || movedSeller || scoreChanged) {
if (addDealToSellerIndexInPipeline(pipeline, next)) commandCount += 1
}
}
if (!commandCount) return 0
try {
await pipeline.exec()
return commandCount
} catch {
return 0
}
}
async function getRecentDealIdsBySeller({
sellerId,
offset = 0,
limit = 30,
} = {}) {
const sid = normalizePositiveInt(sellerId)
if (!sid) return []
const key = getSellerDealIndexKey(sid)
if (!key) return []
const safeOffset = Math.max(0, Number(offset) || 0)
const safeLimit = Math.max(1, Math.min(Number(limit) || 30, 300))
const redis = getRedisClient()
try {
const ids = await redis.zrevrange(key, safeOffset, safeOffset + safeLimit - 1)
return Array.isArray(ids)
? ids.map((id) => Number(id)).filter((id) => Number.isInteger(id) && id > 0)
: []
} catch {
return []
}
}
async function getSellerDealIndexCount(sellerId) {
const sid = normalizePositiveInt(sellerId)
if (!sid) return 0
const key = getSellerDealIndexKey(sid)
if (!key) return 0
const redis = getRedisClient()
try {
const count = await redis.zcard(key)
return Number.isFinite(Number(count)) ? Number(count) : 0
} catch {
return 0
}
}
module.exports = {
getSellerDealIndexKey,
addDealToSellerIndexInPipeline,
removeDealFromSellerIndexInPipeline,
reconcileDealSellerIndex,
getRecentDealIdsBySeller,
getSellerDealIndexCount,
}