Step 5 migration: add localStorage to DB import flow

This commit is contained in:
Ponte
2026-02-11 15:11:34 +01:00
parent de1da956fc
commit e58ee18936
5 changed files with 208 additions and 15 deletions

View File

@@ -357,6 +357,127 @@ async function toggleGameLoan(id) {
return result.rowCount > 0;
}
async function importCatalog(payload) {
const brands = payload && payload.brands && typeof payload.brands === "object" ? payload.brands : {};
const gamesByConsole =
payload && payload.gamesByConsole && typeof payload.gamesByConsole === "object" ? payload.gamesByConsole : {};
const client = await pool.connect();
let insertedConsoles = 0;
let insertedGames = 0;
try {
await client.query("BEGIN");
for (const [brandNameRaw, consoles] of Object.entries(brands)) {
if (!Array.isArray(consoles)) {
continue;
}
for (const consoleNameRaw of consoles) {
const brandName = normalizeText(brandNameRaw).toUpperCase();
const consoleName = normalizeText(consoleNameRaw);
if (!brandName || !consoleName) {
continue;
}
const existingConsole = await client.query(
`
SELECT c.id
FROM consoles c
JOIN brands b ON b.id = c.brand_id
WHERE b.name = $1 AND c.name = $2
LIMIT 1;
`,
[brandName, consoleName],
);
const result = await ensureConsole(client, brandNameRaw, consoleNameRaw);
if (result && result.consoleId && !existingConsole.rowCount) {
insertedConsoles += 1;
}
}
}
for (const [consoleNameRaw, games] of Object.entries(gamesByConsole)) {
if (!Array.isArray(games) || !games.length) {
continue;
}
const consoleName = normalizeText(consoleNameRaw);
if (!consoleName) {
continue;
}
const consoleRow = await client.query(
`
SELECT c.id, b.name AS brand_name
FROM consoles c
JOIN brands b ON b.id = c.brand_id
WHERE c.name = $1
ORDER BY c.id ASC
LIMIT 1;
`,
[consoleName],
);
if (!consoleRow.rowCount) {
continue;
}
const consoleId = consoleRow.rows[0].id;
for (const game of games) {
const title = normalizeText(game && game.title);
if (!title) {
continue;
}
const genre = normalizeText(game && game.genre) || null;
const publisher = normalizeText(game && game.publisher) || null;
const loanedTo = normalizeText(game && game.loanedTo) || null;
const year = game && game.year != null && game.year !== "" ? Number(game.year) : null;
const value = game && game.value != null && game.value !== "" ? Number(game.value) : null;
const dedupeResult = await client.query(
`
SELECT 1
FROM games
WHERE console_id = $1
AND LOWER(title) = LOWER($2)
AND COALESCE(release_year, 0) = COALESCE($3, 0)
LIMIT 1;
`,
[consoleId, title, year],
);
if (dedupeResult.rowCount) {
continue;
}
await client.query(
`
INSERT INTO games(
console_id, title, genre, publisher, release_year, estimated_value, loaned_to
)
VALUES ($1, $2, $3, $4, $5, $6, $7);
`,
[consoleId, title, genre, publisher, year, value, loanedTo],
);
insertedGames += 1;
}
}
await client.query("COMMIT");
return { insertedConsoles, insertedGames };
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
async function handleRequest(request, response) {
const url = new URL(request.url || "/", `http://${request.headers.host || "localhost"}`);
@@ -432,6 +553,17 @@ async function handleRequest(request, response) {
return;
}
if (request.method === "POST" && url.pathname === "/api/catalog/import") {
try {
const body = await readJsonBody(request);
const result = await importCatalog(body);
sendJson(response, 200, { status: "ok", ...result });
} catch (error) {
sendJson(response, 400, { status: "error", message: error.message });
}
return;
}
const gameIdMatch = url.pathname.match(/^\/api\/catalog\/games\/([0-9a-fA-F-]+)$/);
if (request.method === "PUT" && gameIdMatch) {
try {