fix(library): surface comic vine api errors + sync summary transparency

This commit is contained in:
afiqzudinhadi 2026-08-05 13:13:49 +08:00
parent f146de1b76
commit d76ed2631b
5 changed files with 98 additions and 6 deletions

View file

@ -146,17 +146,25 @@ export class LibraryController {
}
}
/** Surfaces every counter, not just synced/written/flipped -- a run where fetches are
* silently failing (e.g. Comic Vine error envelope) must not read as an empty-but-clean
* success just because skippedNoData/errors were left out of the message. */
private buildSyncSummary(spec: MediaTypeSpec, mode: string, report: LibraryReport): string {
let msg = `Library ${mode}sync (${spec.typeName}): ${report.scanned} scanned, ${report.synced} ok, ${report.written} updated, ${report.flipped.length} flipped`;
if (report.skippedStatic) msg += `, ${report.skippedStatic} static`;
if (report.skippedNoId) msg += `, ${report.skippedNoId} no-id`;
if (report.skippedNoData) msg += `, ${report.skippedNoData} no-data (see console)`;
if (report.errors.length) msg += `, ${report.errors.length} errors (see console)`;
return msg;
}
private async runSync(spec: MediaTypeSpec, full: boolean, dryRun: boolean, quiet: boolean): Promise<LibraryReport> {
this.warnMissingKey(spec, quiet);
const deps = this.makeDeps(spec, dryRun);
const report = await libraryFolderSync(spec, deps, { full, dryRun });
const mode = dryRun ? 'DRY-RUN ' : '';
if (shouldNotifySync(quiet, report.written, report.errors.length)) {
this.notify(
`Library ${mode}sync (${spec.typeName}): ${report.synced} checked, ${report.written} updated, ${report.flipped.length} flipped` +
(report.errors.length ? `, ${report.errors.length} errors (see console)` : ''),
0,
);
this.notify(this.buildSyncSummary(spec, mode, report), 0);
}
return report;
}

View file

@ -139,9 +139,27 @@ export function renderComic(r: ComicRecord, myNotes: string, customSections: Cus
return fm.join('\n') + '\n' + b.join('\n');
}
async function fetchVolumeResults(query: string, key: string, deps: SpecDeps): Promise<any[]> {
/**
* Comic Vine returns HTTP 200 even on auth/request failures -- the real signal is the
* envelope's `status_code` (1 = OK; anything else carries a human-readable `error`, e.g.
* "Invalid API Key"). Left un-checked, callers see empty `results` and treat it as
* "nothing found" instead of a real failure. `status_code` absent entirely (as in older
* hand-built test fixtures) is treated as OK -- only an explicit non-1 code trips this.
*/
function checkEnvelope(data: any, deps: SpecDeps): boolean {
if (data && data.status_code !== undefined && data.status_code !== 1) {
const msg = data.error || 'Unknown error';
deps.log(`Comic Vine error: ${msg}`);
deps.notify(`Comic Vine: ${msg}`);
return false;
}
return true;
}
async function fetchVolumeResults(query: string, key: string, deps: SpecDeps): Promise<any[] | null> {
const qs = new URLSearchParams({ api_key: key, format: 'json', filter: `name:${query}`, limit: '10' });
const res = await deps.http(`${COMICVINE_BASE}/volumes/?${qs.toString()}`, {});
if (!checkEnvelope(res, deps)) return null;
return Array.isArray(res?.results) ? res.results : [];
}
@ -183,6 +201,7 @@ export const comicSpec: MediaTypeSpec = {
if (!query) return null;
try {
const results = await fetchVolumeResults(query, key, deps);
if (results === null) return null; // envelope error already logged/notified
if (results.length === 0) return null;
const q = query.toLowerCase();
const exacts = results.filter(r => String(r.name ?? '').toLowerCase() === q);
@ -218,6 +237,8 @@ export const comicSpec: MediaTypeSpec = {
return null;
}
if (!checkEnvelope(json, deps)) return null;
const result = json?.results;
if (!result || !result.name || (result.id != null && String(result.id) !== id)) {
// identity-guard: never silently swap to a different volume's data; leave the note