refactor: drop Mongo class discriminator + delete src/models/

Inline trivial factory bodies into the repos and scrapers that used them.
The class:/_id: fields were Java-Mongo parity artifacts that nothing
in this codebase reads — Redis docs with the old fields still parse
fine; the next write drops them.
This commit is contained in:
2026-05-09 22:30:48 +07:00
parent 2000b85bbc
commit 8554b72b0b
8 changed files with 38 additions and 123 deletions
+1 -6
View File
@@ -1,7 +1,5 @@
import store from 'app-store-scraper';
import { newAppleApp } from '../models/apple-app.js';
// Mirrors Java AppStoreScraper (api/apple/AppStoreScraper.java).
// Calls the `app-store-scraper` npm lib directly (no HTTP roundtrip).
export function buildAppleRequestByTrackId(id, country) {
@@ -20,9 +18,6 @@ export function createAppleScraper(config, repository) {
return store.app(req);
}
// rawApp returns a JSON-text representation of the parsed object so the
// /rawappleapp command and any other text consumers stay parity-compatible
// with the previous HTTP-text response.
async function rawApp(req) {
return JSON.stringify(await app(req));
}
@@ -30,7 +25,7 @@ export function createAppleScraper(config, repository) {
async function cache(resp) {
if (!resp || !resp.appId) return;
try {
await repo.save(newAppleApp(resp.appId, resp, Date.now()));
await repo.save({ _id: resp.appId, app: resp, millis: Date.now() });
} catch (err) {
logger.warn({ appId: resp.appId, err: err.message }, 'failed to cache apple app');
}
+1 -6
View File
@@ -1,7 +1,5 @@
import gplay from 'google-play-scraper';
import { newGoogleApp } from '../models/google-app.js';
// Mirrors Java GooglePlayScraper (api/google/GooglePlayScraper.java).
// Calls the `google-play-scraper` npm lib directly (no HTTP roundtrip).
export function buildGoogleRequest(appId, country) {
@@ -16,9 +14,6 @@ export function createGoogleScraper(config, repository) {
return gplay.app(req);
}
// rawApp returns a JSON-text representation of the parsed object so the
// /rawgoogleapp command and any other text consumers stay parity-compatible
// with the previous HTTP-text response.
async function rawApp(req) {
return JSON.stringify(await app(req));
}
@@ -28,7 +23,7 @@ export function createGoogleScraper(config, repository) {
const id = resp.appId || fallbackId;
if (!id) return;
try {
await repo.save(newGoogleApp(id, resp, Date.now()));
await repo.save({ _id: id, app: resp, millis: Date.now() });
} catch (err) {
logger.warn({ appId: id, err: err.message }, 'failed to cache google app');
}
-23
View File
@@ -1,23 +0,0 @@
// Admin singleton document — Java parity (_id="admin", class="Admin").
export const ADMIN_ID = 'admin';
export function newAdmin() {
return { _id: ADMIN_ID, class: 'Admin', groups: [] };
}
export function adminAddGroup(admin, groupId) {
if (admin.groups.includes(groupId)) return false;
admin.groups.push(groupId);
return true;
}
export function adminRemoveGroup(admin, groupId) {
const i = admin.groups.indexOf(groupId);
if (i < 0) return false;
admin.groups.splice(i, 1);
return true;
}
export function adminHasGroup(admin, groupId) {
return admin.groups.includes(groupId);
}
-5
View File
@@ -1,5 +0,0 @@
// AppleApp cache entry — Java parity (_id=appId, class="AppleApp").
// TTL is enforced by Upstash Redis EX, so no isExpired helper.
export function newAppleApp(appId, response, millis) {
return { _id: appId, class: 'AppleApp', app: response, millis };
}
-5
View File
@@ -1,5 +0,0 @@
// GoogleApp cache entry — Java parity (_id=appId, class="GoogleApp").
// TTL is enforced by Upstash Redis EX, so no isExpired helper.
export function newGoogleApp(appId, response, millis) {
return { _id: appId, class: 'GoogleApp', app: response, millis };
}
-46
View File
@@ -1,46 +0,0 @@
// Group document — Java parity (_id is string form of Telegram chat ID).
export function groupIdToKey(groupId) {
return String(groupId);
}
export function groupKeyToId(key) {
return Number(key);
}
export function newGroup(groupId) {
return {
_id: groupIdToKey(groupId),
class: 'Group',
appleApps: [],
googleApps: [],
};
}
function addApp(list, appId, country) {
if (list.some((a) => a.appId === appId)) return false;
list.push({ appId, country });
return true;
}
function removeApp(list, appId) {
const i = list.findIndex((a) => a.appId === appId);
if (i < 0) return false;
list.splice(i, 1);
return true;
}
export function groupAddAppleApp(group, appId, country) {
return addApp(group.appleApps, appId, country);
}
export function groupRemoveAppleApp(group, appId) {
return removeApp(group.appleApps, appId);
}
export function groupAddGoogleApp(group, appId, country) {
return addApp(group.googleApps, appId, country);
}
export function groupRemoveGoogleApp(group, appId) {
return removeApp(group.googleApps, appId);
}
+14 -18
View File
@@ -1,48 +1,44 @@
import { getJson, putJson } from './upstash.js';
import {
ADMIN_ID,
adminAddGroup,
adminHasGroup,
adminRemoveGroup,
newAdmin,
} from '../models/admin.js';
// Upstash-backed admin singleton — Java parity at the document level
// (logical key 'admin' holds the same shape Mongo stored at _id="admin").
// The physical Redis key carries the configured KEY_PREFIX (handled by adapter).
const ADMIN_KEY = 'admin';
// Upstash-backed admin singleton. Holds the authorized chat ID allowlist.
export function createAdminRepository(handle) {
async function init() {
const existing = await getJson(handle, ADMIN_ID);
const existing = await getJson(handle, ADMIN_KEY);
if (existing) return;
await save(newAdmin());
await save({ _id: ADMIN_KEY, groups: [] });
}
async function getAdmin() {
const doc = await getJson(handle, ADMIN_ID);
return doc ?? newAdmin();
const doc = await getJson(handle, ADMIN_KEY);
return doc ?? { _id: ADMIN_KEY, groups: [] };
}
async function save(admin) {
await putJson(handle, ADMIN_ID, admin);
await putJson(handle, ADMIN_KEY, admin);
}
async function addGroup(groupId) {
const admin = await getAdmin();
if (!adminAddGroup(admin, groupId)) return false;
if (admin.groups.includes(groupId)) return false;
admin.groups.push(groupId);
await save(admin);
return true;
}
async function removeGroup(groupId) {
const admin = await getAdmin();
if (!adminRemoveGroup(admin, groupId)) return false;
const i = admin.groups.indexOf(groupId);
if (i < 0) return false;
admin.groups.splice(i, 1);
await save(admin);
return true;
}
async function hasGroup(groupId) {
const admin = await getAdmin();
return adminHasGroup(admin, groupId);
return admin.groups.includes(groupId);
}
async function getAllGroups() {
+22 -14
View File
@@ -1,18 +1,13 @@
import { del, getJson, putJson } from './upstash.js';
import {
groupAddAppleApp,
groupAddGoogleApp,
groupIdToKey,
groupRemoveAppleApp,
groupRemoveGoogleApp,
newGroup,
} from '../models/group.js';
// Upstash-backed per-group state. Logical key shape: `group:{chatId}`.
// Physical key gets KEY_PREFIX prepended by the adapter.
export function createGroupRepository(handle) {
function key(groupId) {
return `group:${groupIdToKey(groupId)}`;
return `group:${String(groupId)}`;
}
function newGroup(groupId) {
return { _id: String(groupId), appleApps: [], googleApps: [] };
}
async function exists(groupId) {
@@ -38,6 +33,19 @@ export function createGroupRepository(handle) {
await del(handle, key(groupId));
}
function addApp(list, appId, country) {
if (list.some((a) => a.appId === appId)) return false;
list.push({ appId, country });
return true;
}
function removeApp(list, appId) {
const i = list.findIndex((a) => a.appId === appId);
if (i < 0) return false;
list.splice(i, 1);
return true;
}
async function mutateAndSave(groupId, mutator) {
const group = await getGroup(groupId);
if (!mutator(group)) return false;
@@ -52,12 +60,12 @@ export function createGroupRepository(handle) {
initGroup,
deleteGroup,
addAppleApp: (groupId, appId, country) =>
mutateAndSave(groupId, (g) => groupAddAppleApp(g, appId, country)),
mutateAndSave(groupId, (g) => addApp(g.appleApps, appId, country)),
removeAppleApp: (groupId, appId) =>
mutateAndSave(groupId, (g) => groupRemoveAppleApp(g, appId)),
mutateAndSave(groupId, (g) => removeApp(g.appleApps, appId)),
addGoogleApp: (groupId, appId, country) =>
mutateAndSave(groupId, (g) => groupAddGoogleApp(g, appId, country)),
mutateAndSave(groupId, (g) => addApp(g.googleApps, appId, country)),
removeGoogleApp: (groupId, appId) =>
mutateAndSave(groupId, (g) => groupRemoveGoogleApp(g, appId)),
mutateAndSave(groupId, (g) => removeApp(g.googleApps, appId)),
};
}