mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(server): normalize Calibre indexes and restore image builds
Normalize integer-like Calibre OPF positions while preserving exact fractional labels. Make the static reply decoration explicit so Nest accepts production container builds. Fixes #1212
This commit is contained in:
@@ -29,6 +29,7 @@ describe('formatSeriesIndex', () => {
|
||||
it('validates and parses exact labels without normalizing their digits', () => {
|
||||
expect(['5.10', '01', '0.5'].every(isValidSeriesIndex)).toBe(true);
|
||||
expect(parseSeriesIndex(' 5.10 ')).toBe('5.10');
|
||||
expect(parseSeriesIndex('1.0')).toBe('1.0');
|
||||
expect(parseSeriesIndex(5.1)).toBe('5.1');
|
||||
expect(['', ' ', '-1', '1e2', '1.2.3', '123456789012345678901'].some(isValidSeriesIndex)).toBe(false);
|
||||
});
|
||||
|
||||
+4
-1
@@ -107,7 +107,10 @@ async function bootstrap() {
|
||||
if (!shouldServeSpaFallback(request.url)) {
|
||||
return reply.status(404).send({ statusCode: 404, message: 'Not Found', path: request.url });
|
||||
}
|
||||
return reply.sendFile('index.html');
|
||||
// Nest's SWC type checker does not retain @fastify/static's reply augmentation
|
||||
// through the adapter callback, even though the plugin decorates this reply at runtime.
|
||||
const staticReply = reply as typeof reply & { sendFile(filename: string): typeof reply };
|
||||
return staticReply.sendFile('index.html');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -340,13 +340,28 @@ describe('parseOpf', () => {
|
||||
expect(r.seriesIndex).toBe('1');
|
||||
});
|
||||
|
||||
it('parses fractional series index', () => {
|
||||
it.each([
|
||||
['1.0', '1'],
|
||||
['2.0', '2'],
|
||||
['10.0', '10'],
|
||||
['0.0', '0'],
|
||||
['01.0', '01'],
|
||||
['1', '1'],
|
||||
['1.5', '1.5'],
|
||||
['5.10', '5.10'],
|
||||
['5.01', '5.01'],
|
||||
['1.00', '1.00'],
|
||||
[' 1.0 ', '1'],
|
||||
['-1.0', null],
|
||||
['1e2', null],
|
||||
['1.0.0', null],
|
||||
])('maps Calibre series index %s to %s', (embedded, expected) => {
|
||||
const xml = epub2Opf(`
|
||||
<meta name="calibre:series" content="Discworld"/>
|
||||
<meta name="calibre:series_index" content="5.10"/>
|
||||
<meta name="calibre:series_index" content="${embedded}"/>
|
||||
`);
|
||||
const r = parseOpf(xml);
|
||||
expect(r.seriesIndex).toBe('5.10');
|
||||
|
||||
expect(parseOpf(xml).seriesIndex).toBe(expected);
|
||||
});
|
||||
|
||||
it('parses EPUB3 belongs-to-collection series', () => {
|
||||
@@ -360,6 +375,16 @@ describe('parseOpf', () => {
|
||||
expect(r.seriesIndex).toBe('1');
|
||||
});
|
||||
|
||||
it('preserves an exact EPUB3 group-position ending in .0', () => {
|
||||
const xml = epub3Opf(`
|
||||
<meta id="series" property="belongs-to-collection">Dune Chronicles</meta>
|
||||
<meta refines="#series" property="collection-type">series</meta>
|
||||
<meta refines="#series" property="group-position">1.0</meta>
|
||||
`);
|
||||
|
||||
expect(parseOpf(xml).seriesIndex).toBe('1.0');
|
||||
});
|
||||
|
||||
it('Calibre series takes precedence over EPUB3 belongs-to-collection', () => {
|
||||
const xml = epub3Opf(`
|
||||
<meta name="calibre:series" content="Calibre Series"/>
|
||||
|
||||
@@ -81,6 +81,15 @@ function parseNumber(raw: string | null): number | null {
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function parseCalibreSeriesIndex(raw: string): string | null {
|
||||
const parsed = parseSeriesIndex(raw);
|
||||
if (parsed === null) return null;
|
||||
|
||||
// Calibre serializes whole-number series positions with a single `.0`; other
|
||||
// fractional digits are exact user-facing labels and must remain untouched.
|
||||
return /^\d+\.0$/.test(parsed) ? parsed.slice(0, -2) : parsed;
|
||||
}
|
||||
|
||||
function parseBookOrbitTags(raw: string | null): string[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
@@ -461,7 +470,7 @@ export function parseOpf(xml: string): ParsedOpf {
|
||||
let seriesIndex: string | null = null;
|
||||
const rawSeriesIdx = namedMeta('calibre:series_index');
|
||||
if (rawSeriesIdx) {
|
||||
seriesIndex = parseSeriesIndex(rawSeriesIdx);
|
||||
seriesIndex = parseCalibreSeriesIndex(rawSeriesIdx);
|
||||
}
|
||||
|
||||
if (!seriesName) {
|
||||
|
||||
Reference in New Issue
Block a user