feat: implement bff layer
This commit is contained in:
+87
-55
@@ -1,18 +1,14 @@
|
||||
/**
|
||||
* Data access layer for Other Documents.
|
||||
*
|
||||
* Uses dedicated Prisma models:
|
||||
* - OtherDocumentMeta (collection: other_document_meta)
|
||||
* - OtherDocumentChapter (collection: other_document_chapters)
|
||||
*
|
||||
* Calls the BFF API for all data access (no direct Prisma usage).
|
||||
* Falls back to reading local JSON files from
|
||||
* lib/jsons/other-documents/<slug>/ when the database is unavailable.
|
||||
* lib/jsons/other-documents/<slug>/ when the BFF is unavailable.
|
||||
*
|
||||
* Book codes are plain slugs (e.g. "analysis-bg", "real-peace-formula").
|
||||
*/
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import type { JsonValue } from "@prisma/client/runtime/library";
|
||||
import { bff, bffList } from "@/lib/bff";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
@@ -72,6 +68,33 @@ export type OtherDocumentSidebarItem = {
|
||||
chapter: number;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BFF response types (snake_case from Go)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type BffDocumentMeta = {
|
||||
id: string;
|
||||
book: string;
|
||||
data: {
|
||||
title: string;
|
||||
author: string;
|
||||
year?: number | null;
|
||||
description?: string | null;
|
||||
totalChapters: number;
|
||||
chapters: { number: number; title: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
type BffChapter = {
|
||||
id: string;
|
||||
book: string;
|
||||
chapter: number;
|
||||
title: string;
|
||||
type: string;
|
||||
slug: string;
|
||||
paragraphs: string[];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HTML sanitisation (same pattern used across all readers)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -90,23 +113,52 @@ function sanitizeHtml(html: string): string {
|
||||
return clean;
|
||||
}
|
||||
|
||||
function normalizeParagraphs(paragraphs: string[] | JsonValue): string[] {
|
||||
let result: string[];
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mappers (BFF response → frontend types)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
if (typeof paragraphs === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(paragraphs);
|
||||
result = Array.isArray(parsed) ? parsed : [paragraphs];
|
||||
} catch {
|
||||
result = [paragraphs];
|
||||
}
|
||||
} else if (Array.isArray(paragraphs)) {
|
||||
result = paragraphs as string[];
|
||||
} else {
|
||||
result = [];
|
||||
}
|
||||
function mapMetaData(raw: BffDocumentMeta): OtherDocumentMetaData {
|
||||
const data =
|
||||
raw.data && typeof raw.data === "object"
|
||||
? raw.data
|
||||
: ({} as BffDocumentMeta["data"]);
|
||||
return {
|
||||
book: raw.book,
|
||||
title: data.title ?? "",
|
||||
author: data.author ?? "",
|
||||
year: data.year ?? null,
|
||||
description: data.description ?? null,
|
||||
totalChapters: data.totalChapters ?? 0,
|
||||
chapters: (data.chapters ?? []).map((ch) => ({
|
||||
number: ch.number,
|
||||
title: ch.title,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return result.map(sanitizeHtml);
|
||||
function mapSummary(raw: BffDocumentMeta): OtherDocumentSummary {
|
||||
const data =
|
||||
raw.data && typeof raw.data === "object"
|
||||
? raw.data
|
||||
: ({} as BffDocumentMeta["data"]);
|
||||
return {
|
||||
book: raw.book,
|
||||
title: data.title ?? "",
|
||||
author: data.author ?? "",
|
||||
year: data.year ?? null,
|
||||
description: data.description ?? null,
|
||||
totalChapters: data.totalChapters ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
function mapChapter(raw: BffChapter): OtherDocumentChapterData {
|
||||
return {
|
||||
book: raw.book,
|
||||
chapter: raw.chapter,
|
||||
title: raw.title,
|
||||
type: raw.type,
|
||||
paragraphs: (raw.paragraphs ?? []).map(sanitizeHtml),
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -195,22 +247,12 @@ function getLocalChapter(
|
||||
*/
|
||||
export async function getAllOtherDocuments(): Promise<OtherDocumentSummary[]> {
|
||||
try {
|
||||
const results = await prisma.otherDocumentMeta.findMany({
|
||||
orderBy: { book: "asc" },
|
||||
const results = await bffList<BffDocumentMeta>("/other-documents", {
|
||||
tags: ["other-documents"],
|
||||
});
|
||||
|
||||
if (results.length > 0) {
|
||||
return results.map((row) => {
|
||||
const data = row.data as unknown as OtherDocumentMetaData;
|
||||
return {
|
||||
book: row.book,
|
||||
title: data.title,
|
||||
author: data.author,
|
||||
year: data.year ?? null,
|
||||
description: data.description ?? null,
|
||||
totalChapters: data.totalChapters,
|
||||
};
|
||||
});
|
||||
return results.map(mapSummary);
|
||||
}
|
||||
} catch {
|
||||
// fall through to local JSON
|
||||
@@ -234,12 +276,12 @@ export async function getOtherDocumentMeta(
|
||||
bookCode: string,
|
||||
): Promise<OtherDocumentMetaData | null> {
|
||||
try {
|
||||
const row = await prisma.otherDocumentMeta.findUnique({
|
||||
where: { book: bookCode },
|
||||
const raw = await bff<BffDocumentMeta>(`/other-documents/${bookCode}`, {
|
||||
tags: [`other-doc-${bookCode}`],
|
||||
});
|
||||
|
||||
if (row) {
|
||||
return row.data as unknown as OtherDocumentMetaData;
|
||||
if (raw) {
|
||||
return mapMetaData(raw);
|
||||
}
|
||||
} catch {
|
||||
// fall through to local JSON
|
||||
@@ -258,23 +300,13 @@ export async function getOtherDocumentChapter(
|
||||
if (chapterNum < 1 || !Number.isInteger(chapterNum)) return null;
|
||||
|
||||
try {
|
||||
const doc = await prisma.otherDocumentChapter.findUnique({
|
||||
where: {
|
||||
book_slug: {
|
||||
book: bookCode,
|
||||
slug: String(chapterNum),
|
||||
},
|
||||
},
|
||||
});
|
||||
const raw = await bff<BffChapter>(
|
||||
`/other-documents/${bookCode}/chapters/${chapterNum}`,
|
||||
{ tags: [`other-doc-${bookCode}`] },
|
||||
);
|
||||
|
||||
if (doc) {
|
||||
return {
|
||||
book: doc.book,
|
||||
chapter: doc.chapter,
|
||||
title: doc.title,
|
||||
type: doc.type,
|
||||
paragraphs: normalizeParagraphs(doc.paragraphs),
|
||||
};
|
||||
if (raw) {
|
||||
return mapChapter(raw);
|
||||
}
|
||||
} catch {
|
||||
// fall through to local JSON
|
||||
|
||||
Reference in New Issue
Block a user