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, }