HotTRDealsBackend/services/redis/userCache.service.js
2026-02-07 22:42:02 +00:00

229 lines
7.3 KiB
JavaScript

const { getRedisClient } = require("./client")
const dealSaveDb = require("../../db/dealSave.db")
const USER_KEY_PREFIX = "users:cache:"
const USER_SAVED_SET_PREFIX = "users:saved:"
const USER_UNSAVED_SET_PREFIX = "users:unsaved:"
const USER_SAVED_HASH_PREFIX = "users:savedmap:"
const DEFAULT_USER_TTL_SECONDS = 60 * 60
function createRedisClient() {
return getRedisClient()
}
function normalizeUserId(userId) {
const id = Number(userId)
return Number.isInteger(id) && id > 0 ? id : null
}
async function ensureUserCache(userId, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
const id = normalizeUserId(userId)
if (!id) return false
const redis = createRedisClient()
const key = `${USER_KEY_PREFIX}${id}`
const savedMapKey = `${USER_SAVED_HASH_PREFIX}${id}`
try {
const exists = await redis.exists(key)
if (!exists) {
await redis.call(
"JSON.SET",
key,
"$",
JSON.stringify({ id, savedDeals: [] })
)
}
if (ttlSeconds) {
await redis.expire(key, Number(ttlSeconds))
await redis.expire(savedMapKey, Number(ttlSeconds))
}
return true
} catch {
return false
} finally {}
}
async function getUserSavedIdsFromRedis(userId) {
const id = normalizeUserId(userId)
if (!id) return []
const redis = createRedisClient()
const key = `${USER_KEY_PREFIX}${id}`
const setKey = `${USER_SAVED_SET_PREFIX}${id}`
const unsavedKey = `${USER_UNSAVED_SET_PREFIX}${id}`
const savedMapKey = `${USER_SAVED_HASH_PREFIX}${id}`
try {
const [raw, setIds, unsavedIds] = await Promise.all([
redis.call("JSON.GET", key, "$.savedDeals"),
redis.smembers(setKey),
redis.smembers(unsavedKey),
])
const fromJson = raw ? JSON.parse(raw) : []
const arr = Array.isArray(fromJson) ? (Array.isArray(fromJson[0]) ? fromJson[0] : fromJson) : []
const jsonIds = arr
.map((v) => Number(v))
.filter((v) => Number.isInteger(v) && v > 0)
const setList = (Array.isArray(setIds) ? setIds : [])
.map((v) => Number(v))
.filter((v) => Number.isInteger(v) && v > 0)
const unsavedList = (Array.isArray(unsavedIds) ? unsavedIds : [])
.map((v) => Number(v))
.filter((v) => Number.isInteger(v) && v > 0)
return {
jsonIds,
savedSet: new Set(setList),
unsavedSet: new Set(unsavedList),
}
} catch {
return { jsonIds: [], savedSet: new Set(), unsavedSet: new Set() }
} finally {}
}
async function getUserSavedMapForDeals(userId, dealIds = []) {
const uid = normalizeUserId(userId)
const ids = Array.from(
new Set((Array.isArray(dealIds) ? dealIds : []).map(Number).filter((id) => Number.isInteger(id) && id > 0))
)
if (!uid || !ids.length) return new Map()
const redis = createRedisClient()
const key = `${USER_SAVED_HASH_PREFIX}${uid}`
try {
const exists = await redis.exists(key)
if (!exists) {
const saved = await dealSaveDb.findDealSavesByUser(uid, {
select: { dealId: true },
where: { userId: uid },
})
const pipeline = redis.pipeline()
saved.forEach((entry) => {
if (!entry?.dealId) return
pipeline.hset(key, String(entry.dealId), "1")
})
pipeline.expire(key, DEFAULT_USER_TTL_SECONDS)
await pipeline.exec()
const savedSet = new Set(saved.map((s) => Number(s.dealId)))
const map = new Map()
ids.forEach((id) => {
if (savedSet.has(id)) map.set(id, true)
})
return map
}
const results = await redis.hmget(key, ids.map(String))
const map = new Map()
results.forEach((raw, idx) => {
const value = raw == null ? 0 : Number(raw)
if (value) map.set(ids[idx], true)
})
return map
} catch {
return new Map()
} finally {}
}
async function setUserSavedDeals(userId, ids = [], { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
const uid = normalizeUserId(userId)
if (!uid) return false
const redis = createRedisClient()
const key = `${USER_KEY_PREFIX}${uid}`
const savedMapKey = `${USER_SAVED_HASH_PREFIX}${uid}`
try {
const list = Array.isArray(ids) ? ids : []
await redis.call("JSON.SET", key, "$.savedDeals", JSON.stringify(list))
await redis.del(savedMapKey)
if (list.length) {
const pipeline = redis.pipeline()
list.forEach((id) => pipeline.hset(savedMapKey, String(id), "1"))
await pipeline.exec()
}
if (ttlSeconds) {
await redis.expire(key, Number(ttlSeconds))
await redis.expire(savedMapKey, Number(ttlSeconds))
}
return true
} catch {
return false
} finally {}
}
async function addUserSavedDeal(userId, dealId, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
const uid = normalizeUserId(userId)
const did = normalizeUserId(dealId)
if (!uid || !did) return false
const redis = createRedisClient()
const key = `${USER_KEY_PREFIX}${uid}`
const setKey = `${USER_SAVED_SET_PREFIX}${uid}`
const unsavedKey = `${USER_UNSAVED_SET_PREFIX}${uid}`
const savedMapKey = `${USER_SAVED_HASH_PREFIX}${uid}`
try {
await ensureUserCache(uid, { ttlSeconds })
const raw = await redis.call("JSON.GET", key, "$.savedDeals")
let list = []
if (raw) {
const parsed = JSON.parse(raw)
const arr = Array.isArray(parsed) ? parsed[0] : []
list = Array.isArray(arr) ? arr : []
}
const exists = Array.isArray(list)
? list.some((v) => Number(v) === did)
: false
if (!exists) {
list = [did, ...list]
await redis.call("JSON.SET", key, "$.savedDeals", JSON.stringify(list))
}
await redis.sadd(setKey, String(did))
await redis.srem(unsavedKey, String(did))
await redis.hset(savedMapKey, String(did), "1")
if (ttlSeconds) {
await redis.expire(setKey, Number(ttlSeconds))
await redis.expire(unsavedKey, Number(ttlSeconds))
await redis.expire(key, Number(ttlSeconds))
await redis.expire(savedMapKey, Number(ttlSeconds))
}
return true
} catch {
return false
} finally {}
}
async function removeUserSavedDeal(userId, dealId, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
const uid = normalizeUserId(userId)
const did = normalizeUserId(dealId)
if (!uid || !did) return false
const redis = createRedisClient()
const key = `${USER_KEY_PREFIX}${uid}`
const setKey = `${USER_SAVED_SET_PREFIX}${uid}`
const unsavedKey = `${USER_UNSAVED_SET_PREFIX}${uid}`
const savedMapKey = `${USER_SAVED_HASH_PREFIX}${uid}`
try {
const raw = await redis.call("JSON.GET", key, "$.savedDeals")
if (raw) {
const parsed = JSON.parse(raw)
const arr = Array.isArray(parsed) ? parsed[0] : []
const list = Array.isArray(arr)
? arr.filter((v) => Number(v) !== did)
: []
await redis.call("JSON.SET", key, "$.savedDeals", JSON.stringify(list))
}
await redis.srem(setKey, String(did))
await redis.sadd(unsavedKey, String(did))
await redis.hdel(savedMapKey, String(did))
if (ttlSeconds) {
await redis.expire(setKey, Number(ttlSeconds))
await redis.expire(unsavedKey, Number(ttlSeconds))
await redis.expire(key, Number(ttlSeconds))
await redis.expire(savedMapKey, Number(ttlSeconds))
}
return true
} catch {
return false
} finally {}
}
module.exports = {
ensureUserCache,
getUserSavedIdsFromRedis,
getUserSavedMapForDeals,
setUserSavedDeals,
addUserSavedDeal,
removeUserSavedDeal,
}