feat(api,shared): métadonnées multi-providers et automatisation des scans/enrichissements
Chaîne de résolution metadata (local, Open Library, Google Books, BNF) avec activation/priorité/clé API par provider, colonnes isbn13 et identifiers sur les livres, et intégration au scanner pour compléter métadonnées et jaquettes manquantes. Module automation: réglages persistés, planifications scan/enrichissement et déclenchement manuel, exposés via des endpoints admin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -56,6 +56,8 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
author TEXT,
|
||||
description TEXT,
|
||||
isbn TEXT,
|
||||
isbn13 TEXT,
|
||||
identifiers_json TEXT,
|
||||
language TEXT,
|
||||
publisher TEXT,
|
||||
published_date TEXT,
|
||||
@ -89,6 +91,26 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS metadata_source_config (
|
||||
provider TEXT PRIMARY KEY CHECK (provider IN ('local','openlibrary','googlebooks','bnf')),
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
priority INTEGER NOT NULL,
|
||||
api_key TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS automation_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
watch_libraries INTEGER NOT NULL DEFAULT 0,
|
||||
auto_enrich_new_books INTEGER NOT NULL DEFAULT 1,
|
||||
isbn_priority_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
scan_schedule_json TEXT NOT NULL,
|
||||
enrich_schedule_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS book_fts USING fts5(
|
||||
title,
|
||||
author,
|
||||
@ -100,6 +122,7 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
|
||||
CREATE INDEX IF NOT EXISTS books_library_idx ON books(library_id);
|
||||
CREATE INDEX IF NOT EXISTS books_title_idx ON books(title);
|
||||
CREATE INDEX IF NOT EXISTS books_isbn13_idx ON books(isbn13);
|
||||
CREATE INDEX IF NOT EXISTS jobs_status_idx ON jobs(status);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS books_ai AFTER INSERT ON books BEGIN
|
||||
@ -120,6 +143,8 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
END;
|
||||
`);
|
||||
this.ensureBooksSupportsComicArchives();
|
||||
this.ensureBooksMetadataColumns();
|
||||
this.ensureMetadataDefaults();
|
||||
this.sqlite.exec("INSERT INTO book_fts(book_fts) VALUES('rebuild')");
|
||||
}
|
||||
|
||||
@ -146,6 +171,8 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
author TEXT,
|
||||
description TEXT,
|
||||
isbn TEXT,
|
||||
isbn13 TEXT,
|
||||
identifiers_json TEXT,
|
||||
language TEXT,
|
||||
publisher TEXT,
|
||||
published_date TEXT,
|
||||
@ -158,11 +185,11 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
INSERT INTO books (
|
||||
id, library_id, title, author, description, isbn, language, publisher, published_date,
|
||||
id, library_id, title, author, description, isbn, isbn13, identifiers_json, language, publisher, published_date,
|
||||
format, file_path, cover_path, file_size, file_mtime, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id, library_id, title, author, description, isbn, language, publisher, published_date,
|
||||
id, library_id, title, author, description, isbn, NULL, NULL, language, publisher, published_date,
|
||||
format, file_path, cover_path, file_size, file_mtime, created_at, updated_at
|
||||
FROM books_legacy_format;
|
||||
DROP TABLE books_legacy_format;
|
||||
@ -174,6 +201,7 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS books_file_path_unique ON books(file_path);
|
||||
CREATE INDEX IF NOT EXISTS books_library_idx ON books(library_id);
|
||||
CREATE INDEX IF NOT EXISTS books_title_idx ON books(title);
|
||||
CREATE INDEX IF NOT EXISTS books_isbn13_idx ON books(isbn13);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS books_ai AFTER INSERT ON books BEGIN
|
||||
INSERT INTO book_fts(rowid, title, author, description, isbn)
|
||||
@ -193,4 +221,47 @@ export class DatabaseService implements OnModuleDestroy {
|
||||
END;
|
||||
`);
|
||||
}
|
||||
|
||||
private ensureBooksMetadataColumns(): void {
|
||||
const columns = this.sqlite.prepare("PRAGMA table_info(books)").all() as Array<{ name: string }>;
|
||||
const names = new Set(columns.map((column) => column.name));
|
||||
if (!names.has("isbn13")) {
|
||||
this.sqlite.exec("ALTER TABLE books ADD COLUMN isbn13 TEXT");
|
||||
}
|
||||
if (!names.has("identifiers_json")) {
|
||||
this.sqlite.exec("ALTER TABLE books ADD COLUMN identifiers_json TEXT");
|
||||
}
|
||||
this.sqlite.exec("CREATE INDEX IF NOT EXISTS books_isbn13_idx ON books(isbn13)");
|
||||
}
|
||||
|
||||
private ensureMetadataDefaults(): void {
|
||||
const now = this.now();
|
||||
const insertSource = this.sqlite.prepare(`
|
||||
INSERT INTO metadata_source_config (provider, enabled, priority, api_key, created_at, updated_at)
|
||||
VALUES (?, ?, ?, NULL, ?, ?)
|
||||
ON CONFLICT(provider) DO NOTHING
|
||||
`);
|
||||
insertSource.run("local", 1, 0, now, now);
|
||||
insertSource.run("openlibrary", this.config.openLibraryEnabled ? 1 : 0, 1, now, now);
|
||||
insertSource.run("googlebooks", 0, 2, now, now);
|
||||
insertSource.run("bnf", 0, 3, now, now);
|
||||
|
||||
this.sqlite
|
||||
.prepare(
|
||||
`
|
||||
INSERT INTO automation_settings (
|
||||
id, watch_libraries, auto_enrich_new_books, isbn_priority_enabled,
|
||||
scan_schedule_json, enrich_schedule_json, created_at, updated_at
|
||||
)
|
||||
VALUES (1, 0, 1, 1, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO NOTHING
|
||||
`
|
||||
)
|
||||
.run(
|
||||
JSON.stringify({ frequency: "disabled", time: "03:00", dayOfWeek: 1 }),
|
||||
JSON.stringify({ frequency: "disabled", time: "04:00", dayOfWeek: 1 }),
|
||||
now,
|
||||
now
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,8 @@ export const books = sqliteTable(
|
||||
author: text("author"),
|
||||
description: text("description"),
|
||||
isbn: text("isbn"),
|
||||
isbn13: text("isbn13"),
|
||||
identifiersJson: text("identifiers_json"),
|
||||
language: text("language"),
|
||||
publisher: text("publisher"),
|
||||
publishedDate: text("published_date"),
|
||||
@ -75,3 +77,23 @@ export const jobs = sqliteTable("jobs", {
|
||||
createdAt: text("created_at").notNull(),
|
||||
updatedAt: text("updated_at").notNull()
|
||||
});
|
||||
|
||||
export const metadataSourceConfig = sqliteTable("metadata_source_config", {
|
||||
provider: text("provider", { enum: ["local", "openlibrary", "googlebooks", "bnf"] }).primaryKey(),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
priority: integer("priority").notNull(),
|
||||
apiKey: text("api_key"),
|
||||
createdAt: text("created_at").notNull(),
|
||||
updatedAt: text("updated_at").notNull()
|
||||
});
|
||||
|
||||
export const automationSettings = sqliteTable("automation_settings", {
|
||||
id: integer("id").primaryKey(),
|
||||
watchLibraries: integer("watch_libraries", { mode: "boolean" }).notNull().default(false),
|
||||
autoEnrichNewBooks: integer("auto_enrich_new_books", { mode: "boolean" }).notNull().default(true),
|
||||
isbnPriorityEnabled: integer("isbn_priority_enabled", { mode: "boolean" }).notNull().default(true),
|
||||
scanScheduleJson: text("scan_schedule_json").notNull(),
|
||||
enrichScheduleJson: text("enrich_schedule_json").notNull(),
|
||||
createdAt: text("created_at").notNull(),
|
||||
updatedAt: text("updated_at").notNull()
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user