@s-hirano-ist/s-scripts 1.23.4 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cleanup-minio-images.js +6 -8
- package/dist/cleanup-minio-images.js.map +1 -1
- package/dist/enrich-books.js +10 -8
- package/dist/enrich-books.js.map +1 -1
- package/dist/fetch-articles.js +9 -7
- package/dist/fetch-articles.js.map +1 -1
- package/dist/fetch-books.js +25 -25
- package/dist/fetch-books.js.map +1 -1
- package/dist/fetch-images.js +23 -23
- package/dist/fetch-images.js.map +1 -1
- package/dist/fetch-notes.js +3 -3
- package/dist/fetch-notes.js.map +1 -1
- package/dist/find-duplicate-json-articles.js +1 -1
- package/dist/find-duplicate-json-articles.js.map +1 -1
- package/dist/fix-book-frontmatter.js +73 -53
- package/dist/fix-book-frontmatter.js.map +1 -1
- package/dist/fix-image-formats.js +3 -3
- package/dist/fix-image-formats.js.map +1 -1
- package/dist/infrastructures/articles-command-repository.d.ts +1 -1
- package/dist/infrastructures/articles-command-repository.d.ts.map +1 -1
- package/dist/infrastructures/articles-command-repository.js +3 -2
- package/dist/infrastructures/articles-command-repository.js.map +1 -1
- package/dist/infrastructures/books-command-repository.d.ts +1 -1
- package/dist/infrastructures/books-command-repository.d.ts.map +1 -1
- package/dist/infrastructures/books-command-repository.js +3 -2
- package/dist/infrastructures/books-command-repository.js.map +1 -1
- package/dist/infrastructures/images-command-repository.d.ts +1 -1
- package/dist/infrastructures/images-command-repository.d.ts.map +1 -1
- package/dist/infrastructures/images-command-repository.js +3 -2
- package/dist/infrastructures/images-command-repository.js.map +1 -1
- package/dist/infrastructures/notes-command-repository.d.ts +1 -1
- package/dist/infrastructures/notes-command-repository.d.ts.map +1 -1
- package/dist/infrastructures/notes-command-repository.js +3 -2
- package/dist/infrastructures/notes-command-repository.js.map +1 -1
- package/dist/ingest-articles.js +129 -106
- package/dist/ingest-articles.js.map +1 -1
- package/dist/ingest-books.js +113 -102
- package/dist/ingest-books.js.map +1 -1
- package/dist/ingest-images.js +8 -8
- package/dist/ingest-images.js.map +1 -1
- package/dist/ingest-notes.js +50 -44
- package/dist/ingest-notes.js.map +1 -1
- package/dist/rag/delete-collection.js.map +1 -1
- package/dist/rag/ingest.js +5 -5
- package/dist/rag/ingest.js.map +1 -1
- package/dist/rag/search.js.map +1 -1
- package/dist/reset-articles.js +1 -1
- package/dist/reset-articles.js.map +1 -1
- package/dist/reset-books.js +1 -1
- package/dist/reset-books.js.map +1 -1
- package/dist/reset-images.js +1 -1
- package/dist/reset-images.js.map +1 -1
- package/dist/reset-notes.js +1 -1
- package/dist/reset-notes.js.map +1 -1
- package/dist/revert-articles.js +1 -1
- package/dist/revert-articles.js.map +1 -1
- package/dist/revert-books.js +1 -1
- package/dist/revert-books.js.map +1 -1
- package/dist/revert-images.js +1 -1
- package/dist/revert-images.js.map +1 -1
- package/dist/revert-notes.js +1 -1
- package/dist/revert-notes.js.map +1 -1
- package/dist/s-content-format.js +1 -1
- package/dist/s-content-format.js.map +1 -1
- package/dist/update-json-articles.js +13 -12
- package/dist/update-json-articles.js.map +1 -1
- package/dist/update-raw-articles.js +10 -9
- package/dist/update-raw-articles.js.map +1 -1
- package/dist/validate-json-articles.js +1 -1
- package/dist/validate-json-articles.js.map +1 -1
- package/package.json +9 -9
package/dist/ingest-articles.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { basename } from "node:path";
|
|
4
2
|
import { makeExportedStatus, makeId, makeUserId, } from "@s-hirano-ist/s-core/shared-kernel/entities/common-entity";
|
|
5
3
|
import { createPushoverService } from "@s-hirano-ist/s-notification";
|
|
6
4
|
import { glob } from "glob";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { basename } from "node:path";
|
|
7
7
|
const SCRIPT_NAME = "ingest-articles";
|
|
8
|
+
function isUnchanged(existing, item, fileQuote, fileOgImageUrl, fileOgTitle, fileOgDescription) {
|
|
9
|
+
return (existing.title === item.title &&
|
|
10
|
+
existing.quote === fileQuote &&
|
|
11
|
+
existing.ogImageUrl === fileOgImageUrl &&
|
|
12
|
+
existing.ogTitle === fileOgTitle &&
|
|
13
|
+
existing.ogDescription === fileOgDescription);
|
|
14
|
+
}
|
|
15
|
+
function outcomeKey(outcome) {
|
|
16
|
+
if (outcome === "inserted")
|
|
17
|
+
return "insertedCount";
|
|
18
|
+
if (outcome === "updated")
|
|
19
|
+
return "updatedCount";
|
|
20
|
+
return "skippedCount";
|
|
21
|
+
}
|
|
8
22
|
async function main() {
|
|
9
23
|
const dryRun = process.argv.includes("--dry-run");
|
|
10
24
|
const env = {
|
|
@@ -29,6 +43,110 @@ async function main() {
|
|
|
29
43
|
const userId = makeUserId(env.USERNAME_TO_EXPORT ?? "");
|
|
30
44
|
const exported = makeExportedStatus();
|
|
31
45
|
const fileUrls = new Set();
|
|
46
|
+
async function processExisting(existing, item) {
|
|
47
|
+
const fileQuote = item.quote ?? null;
|
|
48
|
+
const fileOgImageUrl = item.ogImageUrl ?? null;
|
|
49
|
+
const fileOgTitle = item.ogTitle ?? null;
|
|
50
|
+
const fileOgDescription = item.ogDescription ?? null;
|
|
51
|
+
if (isUnchanged(existing, item, fileQuote, fileOgImageUrl, fileOgTitle, fileOgDescription)) {
|
|
52
|
+
// console.log(
|
|
53
|
+
// `⏭️ スキップ(変更なし): ${item.title} (${item.url})`,
|
|
54
|
+
// );
|
|
55
|
+
return "skipped";
|
|
56
|
+
}
|
|
57
|
+
if (dryRun) {
|
|
58
|
+
console.log(`🔄 [dry-run] 更新予定: ${item.title} (${item.url})`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
await prisma.article.update({
|
|
62
|
+
where: { id: existing.id },
|
|
63
|
+
data: {
|
|
64
|
+
title: item.title,
|
|
65
|
+
quote: fileQuote,
|
|
66
|
+
ogImageUrl: fileOgImageUrl,
|
|
67
|
+
ogTitle: fileOgTitle,
|
|
68
|
+
ogDescription: fileOgDescription,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
console.log(`🔄 更新: ${item.title}`);
|
|
72
|
+
}
|
|
73
|
+
return "updated";
|
|
74
|
+
}
|
|
75
|
+
async function processNew(item, categoryId, existingArticlesMap) {
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
console.log(`🔍 [dry-run] 挿入予定: ${item.title} (${item.url})`);
|
|
78
|
+
existingArticlesMap.set(item.url, {
|
|
79
|
+
id: "",
|
|
80
|
+
url: item.url,
|
|
81
|
+
title: item.title,
|
|
82
|
+
quote: item.quote ?? null,
|
|
83
|
+
ogImageUrl: item.ogImageUrl ?? null,
|
|
84
|
+
ogTitle: item.ogTitle ?? null,
|
|
85
|
+
ogDescription: item.ogDescription ?? null,
|
|
86
|
+
});
|
|
87
|
+
return "inserted";
|
|
88
|
+
}
|
|
89
|
+
await prisma.article.create({
|
|
90
|
+
data: {
|
|
91
|
+
id: String(makeId()),
|
|
92
|
+
title: item.title,
|
|
93
|
+
url: item.url,
|
|
94
|
+
quote: item.quote ?? null,
|
|
95
|
+
ogImageUrl: item.ogImageUrl ?? null,
|
|
96
|
+
ogTitle: item.ogTitle ?? null,
|
|
97
|
+
ogDescription: item.ogDescription ?? null,
|
|
98
|
+
categoryId,
|
|
99
|
+
status: exported.status,
|
|
100
|
+
exportedAt: exported.exportedAt,
|
|
101
|
+
userId,
|
|
102
|
+
createdAt: new Date(),
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
existingArticlesMap.set(item.url, {
|
|
106
|
+
id: "",
|
|
107
|
+
url: item.url,
|
|
108
|
+
title: item.title,
|
|
109
|
+
quote: item.quote ?? null,
|
|
110
|
+
ogImageUrl: item.ogImageUrl ?? null,
|
|
111
|
+
ogTitle: item.ogTitle ?? null,
|
|
112
|
+
ogDescription: item.ogDescription ?? null,
|
|
113
|
+
});
|
|
114
|
+
console.log(`✅ 挿入: ${item.title}`);
|
|
115
|
+
return "inserted";
|
|
116
|
+
}
|
|
117
|
+
async function processItem(item, categoryId, existingArticlesMap) {
|
|
118
|
+
fileUrls.add(item.url);
|
|
119
|
+
const existing = existingArticlesMap.get(item.url);
|
|
120
|
+
if (existing) {
|
|
121
|
+
return processExisting(existing, item);
|
|
122
|
+
}
|
|
123
|
+
return processNew(item, categoryId, existingArticlesMap);
|
|
124
|
+
}
|
|
125
|
+
async function resolveCategoryId(categoryName, categoryMap) {
|
|
126
|
+
const existingId = categoryMap.get(categoryName);
|
|
127
|
+
if (existingId) {
|
|
128
|
+
return { categoryId: existingId, created: false };
|
|
129
|
+
}
|
|
130
|
+
let categoryId;
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
console.log(`🔍 [dry-run] カテゴリ作成予定: ${categoryName}`);
|
|
133
|
+
categoryId = `dry-run-${categoryName}`;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const category = await prisma.category.create({
|
|
137
|
+
data: {
|
|
138
|
+
id: String(makeId()),
|
|
139
|
+
name: categoryName,
|
|
140
|
+
userId,
|
|
141
|
+
createdAt: new Date(),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
categoryId = category.id;
|
|
145
|
+
console.log(`📂 カテゴリ作成: ${categoryName}`);
|
|
146
|
+
}
|
|
147
|
+
categoryMap.set(categoryName, categoryId);
|
|
148
|
+
return { categoryId, created: true };
|
|
149
|
+
}
|
|
32
150
|
async function ingestArticles() {
|
|
33
151
|
const files = await glob(`${contentsPath}/json/article/*.json`);
|
|
34
152
|
console.log(`📁 ${files.length} 件のJSONファイルを検出しました。`);
|
|
@@ -50,9 +168,7 @@ async function main() {
|
|
|
50
168
|
where: { userId },
|
|
51
169
|
});
|
|
52
170
|
const categoryMap = new Map(existingCategories.map((c) => [c.name, c.id]));
|
|
53
|
-
|
|
54
|
-
let updatedCount = 0;
|
|
55
|
-
let skippedCount = 0;
|
|
171
|
+
const counts = { insertedCount: 0, updatedCount: 0, skippedCount: 0 };
|
|
56
172
|
let errorCount = 0;
|
|
57
173
|
let categoryCreatedCount = 0;
|
|
58
174
|
for (const filePath of files) {
|
|
@@ -60,107 +176,14 @@ async function main() {
|
|
|
60
176
|
const content = await readFile(filePath, "utf-8");
|
|
61
177
|
const json = JSON.parse(content);
|
|
62
178
|
const categoryName = json.heading;
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
if (dryRun) {
|
|
66
|
-
console.log(`🔍 [dry-run] カテゴリ作成予定: ${categoryName}`);
|
|
67
|
-
categoryId = `dry-run-${categoryName}`;
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
const category = await prisma.category.create({
|
|
71
|
-
data: {
|
|
72
|
-
id: String(makeId()),
|
|
73
|
-
name: categoryName,
|
|
74
|
-
userId,
|
|
75
|
-
createdAt: new Date(),
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
categoryId = category.id;
|
|
79
|
-
console.log(`📂 カテゴリ作成: ${categoryName}`);
|
|
80
|
-
}
|
|
81
|
-
categoryMap.set(categoryName, categoryId);
|
|
179
|
+
const { categoryId, created } = await resolveCategoryId(categoryName, categoryMap);
|
|
180
|
+
if (created) {
|
|
82
181
|
categoryCreatedCount++;
|
|
83
182
|
}
|
|
84
183
|
for (const item of json.body) {
|
|
85
184
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (existing) {
|
|
89
|
-
const fileQuote = item.quote ?? null;
|
|
90
|
-
const fileOgImageUrl = item.ogImageUrl ?? null;
|
|
91
|
-
const fileOgTitle = item.ogTitle ?? null;
|
|
92
|
-
const fileOgDescription = item.ogDescription ?? null;
|
|
93
|
-
if (existing.title === item.title &&
|
|
94
|
-
existing.quote === fileQuote &&
|
|
95
|
-
existing.ogImageUrl === fileOgImageUrl &&
|
|
96
|
-
existing.ogTitle === fileOgTitle &&
|
|
97
|
-
existing.ogDescription === fileOgDescription) {
|
|
98
|
-
// console.log(
|
|
99
|
-
// `⏭️ スキップ(変更なし): ${item.title} (${item.url})`,
|
|
100
|
-
// );
|
|
101
|
-
skippedCount++;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (dryRun) {
|
|
105
|
-
console.log(`🔄 [dry-run] 更新予定: ${item.title} (${item.url})`);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
await prisma.article.update({
|
|
109
|
-
where: { id: existing.id },
|
|
110
|
-
data: {
|
|
111
|
-
title: item.title,
|
|
112
|
-
quote: fileQuote,
|
|
113
|
-
ogImageUrl: fileOgImageUrl,
|
|
114
|
-
ogTitle: fileOgTitle,
|
|
115
|
-
ogDescription: fileOgDescription,
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
console.log(`🔄 更新: ${item.title}`);
|
|
119
|
-
}
|
|
120
|
-
updatedCount++;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (dryRun) {
|
|
124
|
-
console.log(`🔍 [dry-run] 挿入予定: ${item.title} (${item.url})`);
|
|
125
|
-
insertedCount++;
|
|
126
|
-
existingArticlesMap.set(item.url, {
|
|
127
|
-
id: "",
|
|
128
|
-
url: item.url,
|
|
129
|
-
title: item.title,
|
|
130
|
-
quote: item.quote ?? null,
|
|
131
|
-
ogImageUrl: item.ogImageUrl ?? null,
|
|
132
|
-
ogTitle: item.ogTitle ?? null,
|
|
133
|
-
ogDescription: item.ogDescription ?? null,
|
|
134
|
-
});
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
await prisma.article.create({
|
|
138
|
-
data: {
|
|
139
|
-
id: String(makeId()),
|
|
140
|
-
title: item.title,
|
|
141
|
-
url: item.url,
|
|
142
|
-
quote: item.quote ?? null,
|
|
143
|
-
ogImageUrl: item.ogImageUrl ?? null,
|
|
144
|
-
ogTitle: item.ogTitle ?? null,
|
|
145
|
-
ogDescription: item.ogDescription ?? null,
|
|
146
|
-
categoryId,
|
|
147
|
-
status: exported.status,
|
|
148
|
-
exportedAt: exported.exportedAt,
|
|
149
|
-
userId,
|
|
150
|
-
createdAt: new Date(),
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
insertedCount++;
|
|
154
|
-
existingArticlesMap.set(item.url, {
|
|
155
|
-
id: "",
|
|
156
|
-
url: item.url,
|
|
157
|
-
title: item.title,
|
|
158
|
-
quote: item.quote ?? null,
|
|
159
|
-
ogImageUrl: item.ogImageUrl ?? null,
|
|
160
|
-
ogTitle: item.ogTitle ?? null,
|
|
161
|
-
ogDescription: item.ogDescription ?? null,
|
|
162
|
-
});
|
|
163
|
-
console.log(`✅ 挿入: ${item.title}`);
|
|
185
|
+
const outcome = await processItem(item, categoryId, existingArticlesMap);
|
|
186
|
+
counts[outcomeKey(outcome)]++;
|
|
164
187
|
}
|
|
165
188
|
catch (itemError) {
|
|
166
189
|
console.error(`❌ エラー(${basename(filePath)} > ${item.title}):`, `url(${item.url.length}文字)=${item.url}`, `title(${item.title.length}文字)`, `quote(${(item.quote ?? "").length}文字)`, `ogImageUrl(${(item.ogImageUrl ?? "").length}文字)`, `ogTitle(${(item.ogTitle ?? "").length}文字)`, `ogDescription(${(item.ogDescription ?? "").length}文字)`, itemError);
|
|
@@ -174,9 +197,9 @@ async function main() {
|
|
|
174
197
|
}
|
|
175
198
|
}
|
|
176
199
|
return {
|
|
177
|
-
insertedCount,
|
|
178
|
-
updatedCount,
|
|
179
|
-
skippedCount,
|
|
200
|
+
insertedCount: counts.insertedCount,
|
|
201
|
+
updatedCount: counts.updatedCount,
|
|
202
|
+
skippedCount: counts.skippedCount,
|
|
180
203
|
errorCount,
|
|
181
204
|
categoryCreatedCount,
|
|
182
205
|
};
|
|
@@ -214,7 +237,7 @@ async function main() {
|
|
|
214
237
|
}
|
|
215
238
|
catch (error) {
|
|
216
239
|
console.error("❌ エラーが発生しました:", error);
|
|
217
|
-
await notificationService.notifyError(`${SCRIPT_NAME} failed: ${error}`, {
|
|
240
|
+
await notificationService.notifyError(`${SCRIPT_NAME} failed: ${String(error)}`, {
|
|
218
241
|
caller: SCRIPT_NAME,
|
|
219
242
|
});
|
|
220
243
|
process.exit(1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingest-articles.js","sourceRoot":"","sources":["../src/ingest-articles.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"ingest-articles.js","sourceRoot":"","sources":["../src/ingest-articles.ts"],"names":[],"mappings":";AACA,OAAO,EACN,kBAAkB,EAClB,MAAM,EACN,UAAU,GAEV,MAAM,2DAA2D,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AA0BtC,SAAS,WAAW,CACnB,QAAyB,EACzB,IAAiC,EACjC,SAAwB,EACxB,cAA6B,EAC7B,WAA0B,EAC1B,iBAAgC;IAEhC,OAAO,CACN,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;QAC7B,QAAQ,CAAC,KAAK,KAAK,SAAS;QAC5B,QAAQ,CAAC,UAAU,KAAK,cAAc;QACtC,QAAQ,CAAC,OAAO,KAAK,WAAW;QAChC,QAAQ,CAAC,aAAa,KAAK,iBAAiB,CAC5C,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAClB,OAAoB;IAEpB,IAAI,OAAO,KAAK,UAAU;QAAE,OAAO,eAAe,CAAC;IACnD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC;IACjD,OAAO,cAAc,CAAC;AACvB,CAAC;AAED,KAAK,UAAU,IAAI;IAClB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG;QACX,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;QACtC,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;QACtC,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAChD,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAClD,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;KACzC,CAAC;IAEX,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAElE,8CAA8C;IAC9C,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IAE1D,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;QACjD,GAAG,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE;QAC3B,OAAO,EAAE,GAAG,CAAC,iBAAiB,IAAI,EAAE;QACpC,QAAQ,EAAE,GAAG,CAAC,kBAAkB,IAAI,EAAE;KACtC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAW,UAAU,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IAEtC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,KAAK,UAAU,eAAe,CAC7B,QAAyB,EACzB,IAAiC;QAEjC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC;QACrC,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;QAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;QACzC,MAAM,iBAAiB,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;QAErD,IACC,WAAW,CACV,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,cAAc,EACd,WAAW,EACX,iBAAiB,CACjB,EACA,CAAC;YACF,eAAe;YACf,kDAAkD;YAClD,KAAK;YACL,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACP,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;gBAC3B,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE;gBAC1B,IAAI,EAAE;oBACL,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,KAAK,EAAE,SAAS;oBAChB,UAAU,EAAE,cAAc;oBAC1B,OAAO,EAAE,WAAW;oBACpB,aAAa,EAAE,iBAAiB;iBAChC;aACD,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,KAAK,UAAU,UAAU,CACxB,IAAiC,EACjC,UAAkB,EAClB,mBAAiD;QAEjD,IAAI,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;YAC9D,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;gBACjC,EAAE,EAAE,EAAE;gBACN,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;gBACnC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;gBAC7B,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;aACzC,CAAC,CAAC;YACH,OAAO,UAAU,CAAC;QACnB,CAAC;QAED,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3B,IAAI,EAAE;gBACL,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;gBACpB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;gBACnC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;gBAC7B,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;gBACzC,UAAU;gBACV,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,MAAM;gBACN,SAAS,EAAE,IAAI,IAAI,EAAE;aACrB;SACD,CAAC,CAAC;QACH,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;YACjC,EAAE,EAAE,EAAE;YACN,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;YAC7B,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;SACzC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACnC,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,KAAK,UAAU,WAAW,CACzB,IAAiC,EACjC,UAAkB,EAClB,mBAAiD;QAEjD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEvB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,IAAI,QAAQ,EAAE,CAAC;YACd,OAAO,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,OAAO,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,UAAU,iBAAiB,CAC/B,YAAoB,EACpB,WAAgC;QAEhC,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACjD,IAAI,UAAU,EAAE,CAAC;YAChB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACnD,CAAC;QAED,IAAI,UAAkB,CAAC;QACvB,IAAI,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,0BAA0B,YAAY,EAAE,CAAC,CAAC;YACtD,UAAU,GAAG,WAAW,YAAY,EAAE,CAAC;QACxC,CAAC;aAAM,CAAC;YACP,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC7C,IAAI,EAAE;oBACL,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;oBACpB,IAAI,EAAE,YAAY;oBAClB,MAAM;oBACN,SAAS,EAAE,IAAI,IAAI,EAAE;iBACrB;aACD,CAAC,CAAC;YACH,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,cAAc,YAAY,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC1C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,UAAU,cAAc;QAC5B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,YAAY,sBAAsB,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,MAAM,qBAAqB,CAAC,CAAC;QAErD,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YACtD,KAAK,EAAE,EAAE,MAAM,EAAE;YACjB,MAAM,EAAE;gBACP,EAAE,EAAE,IAAI;gBACR,GAAG,EAAE,IAAI;gBACT,KAAK,EAAE,IAAI;gBACX,KAAK,EAAE,IAAI;gBACX,UAAU,EAAE,IAAI;gBAChB,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,IAAI;aACnB;SACD,CAAC,CAAC;QACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAClC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAkB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CACxD,CAAC;QACF,OAAO,CAAC,GAAG,CACV,WAAW,mBAAmB,CAAC,IAAI,eAAe,CAClD,CAAC;QAEF,MAAM,kBAAkB,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACzD,KAAK,EAAE,EAAE,MAAM,EAAE;SACjB,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAC1B,kBAAkB,CAAC,GAAG,CACrB,CAAC,CAA+B,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAU,CAC5D,CACD,CAAC;QAEF,MAAM,MAAM,GAAG,EAAE,aAAa,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;QACtE,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,oBAAoB,GAAG,CAAC,CAAC;QAE7B,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACJ,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAClD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;gBAEhD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC;gBAClC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,iBAAiB,CACtD,YAAY,EACZ,WAAW,CACX,CAAC;gBACF,IAAI,OAAO,EAAE,CAAC;oBACb,oBAAoB,EAAE,CAAC;gBACxB,CAAC;gBAED,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBAC9B,IAAI,CAAC;wBACJ,MAAM,OAAO,GAAG,MAAM,WAAW,CAChC,IAAI,EACJ,UAAU,EACV,mBAAmB,CACnB,CAAC;wBACF,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;oBAC/B,CAAC;oBAAC,OAAO,SAAS,EAAE,CAAC;wBACpB,OAAO,CAAC,KAAK,CACZ,SAAS,QAAQ,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,EAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,OAAO,IAAI,CAAC,GAAG,EAAE,EACvC,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,EAC/B,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,EACvC,cAAc,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,EACjD,WAAW,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,EAC3C,iBAAiB,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,EACvD,SAAS,CACT,CAAC;wBACF,UAAU,EAAE,CAAC;oBACd,CAAC;gBACF,CAAC;YACF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,OAAO,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACtD,UAAU,EAAE,CAAC;YACd,CAAC;QACF,CAAC;QAED,OAAO;YACN,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,UAAU;YACV,oBAAoB;SACpB,CAAC;IACH,CAAC;IAED,KAAK,UAAU,aAAa;QAC3B,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YACtD,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;YAC1C,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;SAC/B,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CACvC,CAAC,CAA8B,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CACxD,CAAC;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC3B,OAAO,CAAC,CAAC;QACV,CAAC;QAED,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,wBAAwB,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACpD,CAAC;iBAAM,CAAC;gBACP,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC3D,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACxC,CAAC;YACD,YAAY,EAAE,CAAC;QAChB,CAAC;QAED,OAAO,YAAY,CAAC;IACrB,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,EACL,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,oBAAoB,GACpB,GAAG,MAAM,cAAc,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,MAAM,aAAa,EAAE,CAAC;QAC3C,OAAO,CAAC,GAAG,CACV,eAAe,aAAa,UAAU,YAAY,YAAY,YAAY,UAAU,YAAY,WAAW,UAAU,cAAc,oBAAoB,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CACxL,CAAC;QACF,MAAM,mBAAmB,CAAC,UAAU,CAAC,GAAG,WAAW,YAAY,EAAE;YAChE,MAAM,EAAE,WAAW;SACnB,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;QACtC,MAAM,mBAAmB,CAAC,WAAW,CACpC,GAAG,WAAW,YAAY,MAAM,CAAC,KAAK,CAAC,EAAE,EACzC;YACC,MAAM,EAAE,WAAW;SACnB,CACD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;AACF,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
|
package/dist/ingest-books.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { basename, extname } from "node:path";
|
|
4
2
|
import { makeISBN } from "@s-hirano-ist/s-core/books/entities/book-entity";
|
|
5
3
|
import { makeExportedStatus, makeId, makeUserId, } from "@s-hirano-ist/s-core/shared-kernel/entities/common-entity";
|
|
6
4
|
import { createPushoverService } from "@s-hirano-ist/s-notification";
|
|
7
5
|
import { createMinioClient } from "@s-hirano-ist/s-storage";
|
|
8
6
|
import { glob } from "glob";
|
|
9
7
|
import matter from "gray-matter";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { basename, extname } from "node:path";
|
|
10
10
|
import sharp from "sharp";
|
|
11
11
|
const SCRIPT_NAME = "ingest-books";
|
|
12
12
|
const ORIGINAL_BOOK_IMAGE_PATH = "books/original";
|
|
@@ -28,16 +28,26 @@ function getContentType(filePath) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
function arrayEquals(a, b) {
|
|
32
|
+
if (a.length !== b.length)
|
|
33
|
+
return false;
|
|
34
|
+
for (const [i, element] of a.entries()) {
|
|
35
|
+
if (element !== b[i])
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
31
40
|
function parseBookFile(content) {
|
|
32
41
|
const parsed = matter(content);
|
|
33
42
|
const data = parsed.data;
|
|
34
43
|
// title: frontmatter.title を優先、無ければ本文の H1 からフォールバック
|
|
35
44
|
let title = data.title?.trim() ?? "";
|
|
36
45
|
if (!title) {
|
|
37
|
-
const h1Match =
|
|
46
|
+
const h1Match = /^# (.+)$/mu.exec(parsed.content);
|
|
38
47
|
title = h1Match ? h1Match[1].trim() : "";
|
|
39
48
|
}
|
|
40
49
|
const body = parsed.content.trim();
|
|
50
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- gray-matter parses arbitrary YAML; rating can be null at runtime despite the declared type
|
|
41
51
|
if (data.rating === undefined || data.rating === null) {
|
|
42
52
|
throw new Error("rating は必須です (frontmatter に rating フィールドがありません)");
|
|
43
53
|
}
|
|
@@ -53,6 +63,18 @@ function parseBookFile(content) {
|
|
|
53
63
|
googleHref: data.googleHref ?? null,
|
|
54
64
|
};
|
|
55
65
|
}
|
|
66
|
+
function bookMatchesExisting(existing, parsed, expectedImagePath) {
|
|
67
|
+
return (existing.title === parsed.title &&
|
|
68
|
+
existing.markdown === parsed.markdown &&
|
|
69
|
+
existing.imagePath === expectedImagePath &&
|
|
70
|
+
existing.rating === parsed.rating &&
|
|
71
|
+
arrayEquals(existing.tags, parsed.tags) &&
|
|
72
|
+
existing.googleSubTitle === parsed.googleSubTitle &&
|
|
73
|
+
arrayEquals(existing.googleAuthors, parsed.googleAuthors) &&
|
|
74
|
+
existing.googleDescription === parsed.googleDescription &&
|
|
75
|
+
existing.googleImgSrc === parsed.googleImgSrc &&
|
|
76
|
+
existing.googleHref === parsed.googleHref);
|
|
77
|
+
}
|
|
56
78
|
async function main() {
|
|
57
79
|
const dryRun = process.argv.includes("--dry-run");
|
|
58
80
|
const env = {
|
|
@@ -66,11 +88,9 @@ async function main() {
|
|
|
66
88
|
if (Object.values(env).some((v) => !v)) {
|
|
67
89
|
throw new Error("Required environment variables are not set.");
|
|
68
90
|
}
|
|
69
|
-
if (process.env.MINIO_USE_SSL === "true"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
throw new Error("CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET are required when MINIO_USE_SSL is true.");
|
|
73
|
-
}
|
|
91
|
+
if (process.env.MINIO_USE_SSL === "true" &&
|
|
92
|
+
(!process.env.CF_ACCESS_CLIENT_ID || !process.env.CF_ACCESS_CLIENT_SECRET)) {
|
|
93
|
+
throw new Error("CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET are required when MINIO_USE_SSL is true.");
|
|
74
94
|
}
|
|
75
95
|
const contentsPath = process.env.S_CONTENTS_PATH ?? process.cwd();
|
|
76
96
|
// Dynamic import for Prisma ESM compatibility
|
|
@@ -140,21 +160,70 @@ async function main() {
|
|
|
140
160
|
},
|
|
141
161
|
});
|
|
142
162
|
const existingBooksMap = new Map(existingBooks.map((b) => [b.isbn, b]));
|
|
143
|
-
function arrayEquals(a, b) {
|
|
144
|
-
if (a.length !== b.length)
|
|
145
|
-
return false;
|
|
146
|
-
for (let i = 0; i < a.length; i++) {
|
|
147
|
-
if (a[i] !== b[i])
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
163
|
console.log(`📊 DB に ${existingBooksMap.size} 件の既存書籍があります。`);
|
|
153
164
|
let insertedCount = 0;
|
|
154
165
|
let updatedCount = 0;
|
|
155
166
|
let skippedCount = 0;
|
|
156
167
|
let errorCount = 0;
|
|
157
|
-
|
|
168
|
+
async function resolveNewImagePath(localImagePath, fallback) {
|
|
169
|
+
if (!localImagePath)
|
|
170
|
+
return fallback;
|
|
171
|
+
if (dryRun)
|
|
172
|
+
return basename(localImagePath);
|
|
173
|
+
return uploadBookImage(localImagePath);
|
|
174
|
+
}
|
|
175
|
+
async function updateExistingBook(existing, parsed, localImagePath, isbn, title, markdown) {
|
|
176
|
+
const newImagePath = await resolveNewImagePath(localImagePath, existing.imagePath);
|
|
177
|
+
if (dryRun) {
|
|
178
|
+
console.log(`🔄 [dry-run] 更新予定: ${isbn} (${title})`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await prisma.book.update({
|
|
182
|
+
where: { id: existing.id },
|
|
183
|
+
data: {
|
|
184
|
+
title,
|
|
185
|
+
markdown,
|
|
186
|
+
imagePath: newImagePath,
|
|
187
|
+
rating: parsed.rating,
|
|
188
|
+
tags: parsed.tags,
|
|
189
|
+
googleSubTitle: parsed.googleSubTitle,
|
|
190
|
+
googleAuthors: parsed.googleAuthors,
|
|
191
|
+
googleDescription: parsed.googleDescription,
|
|
192
|
+
googleImgSrc: parsed.googleImgSrc,
|
|
193
|
+
googleHref: parsed.googleHref,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
console.log(`🔄 更新: ${isbn} (${title})`);
|
|
197
|
+
}
|
|
198
|
+
async function createNewBook(parsed, localImagePath, isbn, title, markdown) {
|
|
199
|
+
const newImagePath = await resolveNewImagePath(localImagePath, null);
|
|
200
|
+
if (dryRun) {
|
|
201
|
+
console.log(`🔍 [dry-run] 挿入予定: ${isbn} (${title})`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
await prisma.book.create({
|
|
205
|
+
data: {
|
|
206
|
+
id: String(makeId()),
|
|
207
|
+
isbn,
|
|
208
|
+
title,
|
|
209
|
+
markdown,
|
|
210
|
+
imagePath: newImagePath,
|
|
211
|
+
status: exported.status,
|
|
212
|
+
exportedAt: exported.exportedAt,
|
|
213
|
+
userId,
|
|
214
|
+
createdAt: new Date(),
|
|
215
|
+
rating: parsed.rating,
|
|
216
|
+
tags: parsed.tags,
|
|
217
|
+
googleSubTitle: parsed.googleSubTitle,
|
|
218
|
+
googleAuthors: parsed.googleAuthors,
|
|
219
|
+
googleDescription: parsed.googleDescription,
|
|
220
|
+
googleImgSrc: parsed.googleImgSrc,
|
|
221
|
+
googleHref: parsed.googleHref,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
console.log(`✅ 挿入: ${isbn} (${title})`);
|
|
225
|
+
}
|
|
226
|
+
async function processBookFile(filePath) {
|
|
158
227
|
const rawIsbn = basename(filePath, ".md");
|
|
159
228
|
let isbn = rawIsbn;
|
|
160
229
|
let title = "";
|
|
@@ -168,8 +237,7 @@ async function main() {
|
|
|
168
237
|
markdown = parsed.markdown;
|
|
169
238
|
if (!title) {
|
|
170
239
|
console.error(`⚠️ タイトルなし: ${basename(filePath)}`);
|
|
171
|
-
|
|
172
|
-
continue;
|
|
240
|
+
return "error";
|
|
173
241
|
}
|
|
174
242
|
const localImagePath = bookImageMap.get(isbn);
|
|
175
243
|
const existing = existingBooksMap.get(isbn);
|
|
@@ -177,92 +245,35 @@ async function main() {
|
|
|
177
245
|
const expectedImagePath = localImagePath
|
|
178
246
|
? basename(localImagePath)
|
|
179
247
|
: existing.imagePath;
|
|
180
|
-
if (existing
|
|
181
|
-
|
|
182
|
-
existing.imagePath === expectedImagePath &&
|
|
183
|
-
existing.rating === parsed.rating &&
|
|
184
|
-
arrayEquals(existing.tags, parsed.tags) &&
|
|
185
|
-
existing.googleSubTitle === parsed.googleSubTitle &&
|
|
186
|
-
arrayEquals(existing.googleAuthors, parsed.googleAuthors) &&
|
|
187
|
-
existing.googleDescription === parsed.googleDescription &&
|
|
188
|
-
existing.googleImgSrc === parsed.googleImgSrc &&
|
|
189
|
-
existing.googleHref === parsed.googleHref) {
|
|
190
|
-
skippedCount++;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
let newImagePath = existing.imagePath;
|
|
194
|
-
if (localImagePath) {
|
|
195
|
-
if (dryRun) {
|
|
196
|
-
newImagePath = basename(localImagePath);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
newImagePath = await uploadBookImage(localImagePath);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (dryRun) {
|
|
203
|
-
console.log(`🔄 [dry-run] 更新予定: ${isbn} (${title})`);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
await prisma.book.update({
|
|
207
|
-
where: { id: existing.id },
|
|
208
|
-
data: {
|
|
209
|
-
title,
|
|
210
|
-
markdown,
|
|
211
|
-
imagePath: newImagePath,
|
|
212
|
-
rating: parsed.rating,
|
|
213
|
-
tags: parsed.tags,
|
|
214
|
-
googleSubTitle: parsed.googleSubTitle,
|
|
215
|
-
googleAuthors: parsed.googleAuthors,
|
|
216
|
-
googleDescription: parsed.googleDescription,
|
|
217
|
-
googleImgSrc: parsed.googleImgSrc,
|
|
218
|
-
googleHref: parsed.googleHref,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
console.log(`🔄 更新: ${isbn} (${title})`);
|
|
222
|
-
}
|
|
223
|
-
updatedCount++;
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
let newImagePath = null;
|
|
227
|
-
if (localImagePath) {
|
|
228
|
-
if (dryRun) {
|
|
229
|
-
newImagePath = basename(localImagePath);
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
newImagePath = await uploadBookImage(localImagePath);
|
|
248
|
+
if (bookMatchesExisting(existing, parsed, expectedImagePath)) {
|
|
249
|
+
return "skipped";
|
|
233
250
|
}
|
|
251
|
+
await updateExistingBook(existing, parsed, localImagePath, isbn, title, markdown);
|
|
252
|
+
return "updated";
|
|
234
253
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
insertedCount++;
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
await prisma.book.create({
|
|
241
|
-
data: {
|
|
242
|
-
id: String(makeId()),
|
|
243
|
-
isbn,
|
|
244
|
-
title,
|
|
245
|
-
markdown,
|
|
246
|
-
imagePath: newImagePath,
|
|
247
|
-
status: exported.status,
|
|
248
|
-
exportedAt: exported.exportedAt,
|
|
249
|
-
userId,
|
|
250
|
-
createdAt: new Date(),
|
|
251
|
-
rating: parsed.rating,
|
|
252
|
-
tags: parsed.tags,
|
|
253
|
-
googleSubTitle: parsed.googleSubTitle,
|
|
254
|
-
googleAuthors: parsed.googleAuthors,
|
|
255
|
-
googleDescription: parsed.googleDescription,
|
|
256
|
-
googleImgSrc: parsed.googleImgSrc,
|
|
257
|
-
googleHref: parsed.googleHref,
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
insertedCount++;
|
|
261
|
-
console.log(`✅ 挿入: ${isbn} (${title})`);
|
|
254
|
+
await createNewBook(parsed, localImagePath, isbn, title, markdown);
|
|
255
|
+
return "inserted";
|
|
262
256
|
}
|
|
263
257
|
catch (error) {
|
|
264
258
|
console.error(`❌ エラー(${basename(filePath)}):`, `isbn=${isbn}`, `title(${title.length}文字)`, `markdown(${(markdown ?? "").length}文字)`, error);
|
|
265
|
-
|
|
259
|
+
return "error";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
for (const filePath of files) {
|
|
263
|
+
const result = await processBookFile(filePath);
|
|
264
|
+
switch (result) {
|
|
265
|
+
case "inserted":
|
|
266
|
+
insertedCount++;
|
|
267
|
+
break;
|
|
268
|
+
case "updated":
|
|
269
|
+
updatedCount++;
|
|
270
|
+
break;
|
|
271
|
+
case "skipped":
|
|
272
|
+
skippedCount++;
|
|
273
|
+
break;
|
|
274
|
+
case "error":
|
|
275
|
+
errorCount++;
|
|
276
|
+
break;
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
return { insertedCount, updatedCount, skippedCount, errorCount };
|
|
@@ -300,7 +311,7 @@ async function main() {
|
|
|
300
311
|
}
|
|
301
312
|
catch (error) {
|
|
302
313
|
console.error("❌ エラーが発生しました:", error);
|
|
303
|
-
await notificationService.notifyError(`${SCRIPT_NAME} failed: ${error}`, {
|
|
314
|
+
await notificationService.notifyError(`${SCRIPT_NAME} failed: ${String(error)}`, {
|
|
304
315
|
caller: SCRIPT_NAME,
|
|
305
316
|
});
|
|
306
317
|
process.exit(1);
|