feat: split live analysis actions and page catalog loading

Separate incremental live checks from full catalog rebuilds and page projected channel/variant loading so first live-list display stays bounded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-07 23:45:42 +02:00
parent bbb1afb238
commit 4cca030afa
12 changed files with 434 additions and 35 deletions

View File

@ -0,0 +1,125 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Database = require('better-sqlite3');
const liveProjection = require('../server/services/liveProjectionService');
const {
ensureColumns,
LIVE_PROJECTION_COLUMNS,
LIVE_PROJECTION_SCHEMA_SQL
} = require('../server/db/liveProjectionSchema');
function createProjectionDb() {
const db = new Database(':memory:');
db.exec(`
CREATE TABLE categories (
id TEXT PRIMARY KEY,
source_id INTEGER NOT NULL,
category_id TEXT NOT NULL,
type TEXT NOT NULL,
name TEXT NOT NULL,
parent_id TEXT,
is_hidden INTEGER DEFAULT 0,
data JSON
);
CREATE TABLE playlist_items (
id TEXT PRIMARY KEY,
source_id INTEGER NOT NULL,
item_id TEXT NOT NULL,
type TEXT NOT NULL,
name TEXT NOT NULL,
category_id TEXT,
parent_id TEXT,
stream_icon TEXT,
stream_url TEXT,
container_extension TEXT,
rating REAL,
year TEXT,
added_at TEXT,
is_hidden INTEGER DEFAULT 0,
is_favorite INTEGER DEFAULT 0,
data JSON
);
CREATE TABLE favorites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
source_id INTEGER NOT NULL,
item_id TEXT NOT NULL,
item_type TEXT NOT NULL
);
`);
ensureColumns(db, 'playlist_items', LIVE_PROJECTION_COLUMNS);
db.exec(LIVE_PROJECTION_SCHEMA_SQL);
return db;
}
function seedProjectedChannel(db, index) {
const logicalId = `logical:${index}`;
db.prepare(`
INSERT INTO logical_channels
(id, fingerprint, display_name, preferred_variant_id, variant_count, created_at, updated_at)
VALUES (?, ?, ?, ?, 1, 1, 1)
`).run(logicalId, `channel ${index}`, `Channel ${index}`, `1:${index}`);
db.prepare(`
INSERT INTO playlist_items
(id, source_id, item_id, type, name, category_id, stream_icon, stream_url, data,
logical_channel_id, grouping_fingerprint, grouping_input_hash, quality_label, quality_rank,
variant_rank, language_code, language_confidence, language_detector, language_input_hash)
VALUES (?, 1, ?, 'live', ?, 'general', '', ?, '{}', ?, ?, ?, 'HD', 2, 0, 'en', 1, 'laya', ?)
`).run(
`1:${index}`,
String(index),
`Channel ${index}`,
`https://streams.test/${index}.m3u8`,
logicalId,
`channel ${index}`,
`group-hash-${index}`,
`language-hash-${index}`
);
}
test('/api/live/channels projection response is bounded for an oversized catalog', () => {
const db = createProjectionDb();
for (let index = 0; index < 1500; index++) seedProjectedChannel(db, index);
try {
const result = liveProjection.getChannelPage({ includeHidden: false, userId: 7 }, db, {
iptvOrgCatalog: { findMatch: () => null },
epgMatching: { findBestMatch: () => null }
});
assert.ok(result.channels.length <= 250, `unbounded live channel response returned ${result.channels.length} entries`);
} finally {
db.close();
}
});
test('projected live list returns only a bounded variant preview', () => {
const db = createProjectionDb();
const logicalId = 'logical:heavy';
db.prepare(`
INSERT INTO logical_channels
(id, fingerprint, display_name, preferred_variant_id, variant_count, created_at, updated_at)
VALUES (?, 'heavy channel', 'Heavy Channel', '1:0', 2500, 1, 1)
`).run(logicalId);
const insert = db.prepare(`
INSERT INTO playlist_items
(id, source_id, item_id, type, name, category_id, stream_icon, stream_url, data,
logical_channel_id, grouping_fingerprint, grouping_input_hash, quality_label, quality_rank,
variant_rank, language_code, language_confidence, language_detector, language_input_hash)
VALUES (?, 1, ?, 'live', ?, 'general', '', ?, '{}', ?, 'heavy channel', ?, 'HD', 2, ?, 'en', 1, 'laya', ?)
`);
const insertAll = db.transaction(() => {
for (let index = 0; index < 2500; index++) {
insert.run(`1:${index}`, String(index), `Heavy Channel variant ${index}`,
`https://streams.test/${index}.m3u8`, logicalId, `group-${index}`, index, `language-${index}`);
}
});
insertAll();
try {
const result = liveProjection.getChannelPage({ includeHidden: false, userId: 7 }, db);
assert.equal(result.channels.length, 1);
assert.equal(result.channels[0].variantCount, 2500);
assert.ok(result.channels[0].variants.length <= 3);
} finally {
db.close();
}
});