From 55cdc04c57aa7db2b8f2acf807a57ca81acbb860 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 9 May 2026 22:32:56 +0700 Subject: [PATCH] refactor: merge apple/google app caches into one parametrized repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two near-identical per-store repository files collapse into a single createAppCacheRepository(handle, prefix, ttl) factory used twice from app-builder. Scrapers now receive the cache directly (one less layer), and the cache entry shape drops the obsolete _id field — the Redis key already encodes the appId. --- src/api/apple-scraper.js | 25 ++++++++++--------------- src/api/google-scraper.js | 25 ++++++++++--------------- src/app-builder.js | 11 +++++------ src/repository/app-cache-repository.js | 24 ++++++++++++++++++++++++ src/repository/apple-app-repository.js | 25 ------------------------- src/repository/google-app-repository.js | 25 ------------------------- 6 files changed, 49 insertions(+), 86 deletions(-) create mode 100644 src/repository/app-cache-repository.js delete mode 100644 src/repository/apple-app-repository.js delete mode 100644 src/repository/google-app-repository.js diff --git a/src/api/apple-scraper.js b/src/api/apple-scraper.js index ce85197..c2425e3 100644 --- a/src/api/apple-scraper.js +++ b/src/api/apple-scraper.js @@ -10,40 +10,35 @@ export function buildAppleRequestByBundleId(appId, country) { return { appId, country, ratings: true }; } -export function createAppleScraper(config, repository) { +export function createAppleScraper(config, cache) { const { logger } = config; - const repo = repository.appleApp; - - async function app(req) { - return store.app(req); - } async function rawApp(req) { - return JSON.stringify(await app(req)); + return JSON.stringify(await store.app(req)); } - async function cache(resp) { + async function saveCache(resp) { if (!resp || !resp.appId) return; try { - await repo.save({ _id: resp.appId, app: resp, millis: Date.now() }); + await cache.save(resp.appId, resp); } catch (err) { logger.warn({ appId: resp.appId, err: err.message }, 'failed to cache apple app'); } } async function getApp(appId, country) { - const cached = await repo.getCached(appId); + const cached = await cache.getCached(appId); if (cached) return cached.app; - const resp = await app(buildAppleRequestByBundleId(appId, country)); - await cache(resp); + const resp = await store.app(buildAppleRequestByBundleId(appId, country)); + await saveCache(resp); return resp; } async function fetchAndCache(req) { - const resp = await app(req); - await cache(resp); + const resp = await store.app(req); + await saveCache(resp); return resp; } - return { rawApp, app, getApp, fetchAndCache }; + return { rawApp, getApp, fetchAndCache }; } diff --git a/src/api/google-scraper.js b/src/api/google-scraper.js index 2ad8aae..b177991 100644 --- a/src/api/google-scraper.js +++ b/src/api/google-scraper.js @@ -6,42 +6,37 @@ export function buildGoogleRequest(appId, country) { return { appId, country: country || 'vn' }; } -export function createGoogleScraper(config, repository) { +export function createGoogleScraper(config, cache) { const { logger } = config; - const repo = repository.googleApp; - - async function app(req) { - return gplay.app(req); - } async function rawApp(req) { - return JSON.stringify(await app(req)); + return JSON.stringify(await gplay.app(req)); } - async function cache(resp, fallbackId) { + async function saveCache(resp, fallbackId) { if (!resp) return; const id = resp.appId || fallbackId; if (!id) return; try { - await repo.save({ _id: id, app: resp, millis: Date.now() }); + await cache.save(id, resp); } catch (err) { logger.warn({ appId: id, err: err.message }, 'failed to cache google app'); } } async function getApp(appId, country) { - const cached = await repo.getCached(appId); + const cached = await cache.getCached(appId); if (cached) return cached.app; - const resp = await app(buildGoogleRequest(appId, country)); - await cache(resp, appId); + const resp = await gplay.app(buildGoogleRequest(appId, country)); + await saveCache(resp, appId); return resp; } async function fetchAndCache(req) { - const resp = await app(req); - await cache(resp, req.appId); + const resp = await gplay.app(req); + await saveCache(resp, req.appId); return resp; } - return { rawApp, app, getApp, fetchAndCache }; + return { rawApp, getApp, fetchAndCache }; } diff --git a/src/app-builder.js b/src/app-builder.js index c078e42..b7762b8 100644 --- a/src/app-builder.js +++ b/src/app-builder.js @@ -6,8 +6,7 @@ import { loadConfig } from './config.js'; import { createUpstashClient } from './repository/upstash.js'; import { createAdminRepository } from './repository/admin-repository.js'; import { createGroupRepository } from './repository/group-repository.js'; -import { createAppleAppRepository } from './repository/apple-app-repository.js'; -import { createGoogleAppRepository } from './repository/google-app-repository.js'; +import { createAppCacheRepository } from './repository/app-cache-repository.js'; import { createAppleScraper } from './api/apple-scraper.js'; import { createGoogleScraper } from './api/google-scraper.js'; import { createBot } from './bot/bot.js'; @@ -18,11 +17,11 @@ export function buildApp(env) { const store = { admin: createAdminRepository(handle), group: createGroupRepository(handle), - appleApp: createAppleAppRepository(handle, config.appCacheSeconds), - googleApp: createGoogleAppRepository(handle, config.appCacheSeconds), }; - const appleScraper = createAppleScraper(config, store); - const googleScraper = createGoogleScraper(config, store); + const appleCache = createAppCacheRepository(handle, 'apple', config.appCacheSeconds); + const googleCache = createAppCacheRepository(handle, 'google', config.appCacheSeconds); + const appleScraper = createAppleScraper(config, appleCache); + const googleScraper = createGoogleScraper(config, googleCache); const { sender, commands } = createBot(config, store, appleScraper, googleScraper); return { config, store, appleScraper, googleScraper, sender, commands }; } diff --git a/src/repository/app-cache-repository.js b/src/repository/app-cache-repository.js new file mode 100644 index 0000000..937c053 --- /dev/null +++ b/src/repository/app-cache-repository.js @@ -0,0 +1,24 @@ +import { getJson, putJson } from './upstash.js'; + +// Per-store cache for upstream app responses. TTL via Redis EX — +// expired keys are deleted, so a null read is the cache miss. +// `prefix` is the logical key namespace ('apple' or 'google'). +export function createAppCacheRepository(handle, prefix, appCacheSeconds) { + function key(appId) { + return `${prefix}:${appId}`; + } + + return { + async getCached(appId) { + return getJson(handle, key(appId)); + }, + async save(appId, response) { + await putJson( + handle, + key(appId), + { app: response, millis: Date.now() }, + { expirationTtl: appCacheSeconds }, + ); + }, + }; +} diff --git a/src/repository/apple-app-repository.js b/src/repository/apple-app-repository.js deleted file mode 100644 index ae37480..0000000 --- a/src/repository/apple-app-repository.js +++ /dev/null @@ -1,25 +0,0 @@ -import { getJson, putJson } from './upstash.js'; - -// Upstash-backed Apple app cache. Logical key shape: `apple:{appId}`. -// Redis EX (via expirationTtl) replaces Java/Mongo's manual -// `(now - millis) > cacheMillis` check — expired keys are deleted, so a -// get() returning null is the cache miss. -export function createAppleAppRepository(handle, appCacheSeconds) { - function key(appId) { - return `apple:${appId}`; - } - - async function get(appId) { - return getJson(handle, key(appId)); - } - - async function save(entry) { - await putJson(handle, key(entry._id), entry, { expirationTtl: appCacheSeconds }); - } - - async function getCached(appId) { - return get(appId); - } - - return { get, save, getCached }; -} diff --git a/src/repository/google-app-repository.js b/src/repository/google-app-repository.js deleted file mode 100644 index e0e7396..0000000 --- a/src/repository/google-app-repository.js +++ /dev/null @@ -1,25 +0,0 @@ -import { getJson, putJson } from './upstash.js'; - -// Upstash-backed Google app cache. Logical key shape: `google:{appId}`. -// Redis EX (via expirationTtl) replaces Java/Mongo's manual -// `(now - millis) > cacheMillis` check — expired keys are deleted, so a -// get() returning null is the cache miss. -export function createGoogleAppRepository(handle, appCacheSeconds) { - function key(appId) { - return `google:${appId}`; - } - - async function get(appId) { - return getJson(handle, key(appId)); - } - - async function save(entry) { - await putJson(handle, key(entry._id), entry, { expirationTtl: appCacheSeconds }); - } - - async function getCached(appId) { - return get(appId); - } - - return { get, save, getCached }; -}