diff --git a/lib/magazines.ts b/lib/magazines.ts index 37268b78..635c42ee 100644 --- a/lib/magazines.ts +++ b/lib/magazines.ts @@ -208,26 +208,13 @@ export async function getMagazineIssue( where: { book_slug: { book: bookCode, slug } }, }); if (doc) { - // Support both paragraphsJson (string) and paragraphs (array) formats. - // The seed script stores paragraphs as a JSON string to avoid - // MongoDB BSON wire protocol issues with large string arrays. - let paras: string[]; - if (doc.paragraphsJson) { - try { - paras = JSON.parse(doc.paragraphsJson); - } catch { - paras = []; - } - } else { - paras = normalizeParagraphs(doc.paragraphs); - } return { book: doc.book, volume: doc.volume, part: doc.part, title: doc.title, slug: doc.slug, - paragraphs: paras, + paragraphs: normalizeParagraphs(doc.paragraphs), }; } } catch { diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 50719592..aaf6f024 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -224,15 +224,14 @@ model MagazineMeta { // paragraphs: array of HTML paragraph strings (all articles concatenated) // --------------------------------------------------------------------------- model MagazineIssue { - id String @id @default(auto()) @map("_id") @db.ObjectId - book String - volume Int - part Int - title String - slug String - order Int - paragraphs String[] - paragraphsJson String? + id String @id @default(auto()) @map("_id") @db.ObjectId + book String + volume Int + part Int + title String + slug String + order Int + paragraphs String[] @@unique([book, slug]) @@index([book]) diff --git a/scripts/seed-magazines.ts b/scripts/seed-magazines.ts index 87a432b2..d3561c03 100644 --- a/scripts/seed-magazines.ts +++ b/scripts/seed-magazines.ts @@ -107,25 +107,35 @@ async function seedOneMagazine( const rawParas = (issue.paragraphs as string[]) || []; const cleanParas = rawParas.map((p: string) => sanitizeString(String(p))); - // Store paragraphs as a single JSON string to avoid MongoDB BSON - // wire protocol issues with large string arrays. - // The data layer will JSON.parse() this back into an array on read. - const paragraphsJson = JSON.stringify(cleanParas); - - const doc = { + // Insert the base document without paragraphs first, then $set paragraphs. + // This avoids the MongoDB BSON wire protocol "Not null terminated string" + // error that occurs with large string arrays in insertOne/replaceOne. + const baseDoc = { book: bookCode, volume: issue.volume, part: issue.part, title: issue.title, slug: issue.slug, order: issue.order, - paragraphsJson, - paragraphs: [] as string[], // Keep empty array for schema compat + paragraphs: [] as string[], }; - await issuesCol.replaceOne({ book: bookCode, slug: issue.slug }, doc, { - upsert: true, - }); + await issuesCol.replaceOne( + { book: bookCode, slug: issue.slug as string }, + baseDoc, + { upsert: true }, + ); + + // Now set paragraphs in batches using $push to avoid BSON limits + const BATCH_SIZE = 200; + for (let bi = 0; bi < cleanParas.length; bi += BATCH_SIZE) { + const batch = cleanParas.slice(bi, bi + BATCH_SIZE); + await issuesCol.updateOne( + { book: bookCode, slug: issue.slug as string }, + { $push: { paragraphs: { $each: batch } } }, + ); + } + inserted++; } console.log(` 📄 Issues: ${inserted}`);