Import XLSX support: add collection fields and migration script
This commit is contained in:
112
api/server.js
112
api/server.js
@@ -105,13 +105,21 @@ async function runMigrations() {
|
||||
title TEXT NOT NULL,
|
||||
genre TEXT,
|
||||
publisher TEXT,
|
||||
game_version TEXT,
|
||||
is_duplicate BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
release_year INTEGER CHECK (release_year IS NULL OR (release_year >= 1970 AND release_year <= 2100)),
|
||||
purchase_price NUMERIC(10,2) CHECK (purchase_price IS NULL OR purchase_price >= 0),
|
||||
estimated_value NUMERIC(10,2) CHECK (estimated_value IS NULL OR estimated_value >= 0),
|
||||
condition_score NUMERIC(4,2),
|
||||
loaned_to TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
await pool.query("ALTER TABLE games ADD COLUMN IF NOT EXISTS game_version TEXT;");
|
||||
await pool.query("ALTER TABLE games ADD COLUMN IF NOT EXISTS is_duplicate BOOLEAN NOT NULL DEFAULT FALSE;");
|
||||
await pool.query("ALTER TABLE games ADD COLUMN IF NOT EXISTS purchase_price NUMERIC(10,2);");
|
||||
await pool.query("ALTER TABLE games ADD COLUMN IF NOT EXISTS condition_score NUMERIC(4,2);");
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS backup_snapshots (
|
||||
@@ -210,8 +218,12 @@ async function getCatalogFull() {
|
||||
g.title,
|
||||
g.genre,
|
||||
g.publisher,
|
||||
g.game_version,
|
||||
g.is_duplicate,
|
||||
g.release_year,
|
||||
g.purchase_price,
|
||||
g.estimated_value,
|
||||
g.condition_score,
|
||||
g.loaned_to,
|
||||
g.created_at
|
||||
FROM games g
|
||||
@@ -237,8 +249,12 @@ async function getCatalogFull() {
|
||||
title: row.title,
|
||||
genre: row.genre || "",
|
||||
publisher: row.publisher || "",
|
||||
version: row.game_version || "",
|
||||
isDuplicate: Boolean(row.is_duplicate),
|
||||
year: row.release_year || null,
|
||||
purchasePrice: row.purchase_price != null ? Number(row.purchase_price) : null,
|
||||
value: row.estimated_value != null ? Number(row.estimated_value) : null,
|
||||
condition: row.condition_score != null ? Number(row.condition_score) : null,
|
||||
loanedTo: row.loaned_to || "",
|
||||
createdAt: row.created_at,
|
||||
});
|
||||
@@ -272,8 +288,12 @@ async function exportCatalogDumpWithClient(client) {
|
||||
g.title,
|
||||
g.genre,
|
||||
g.publisher,
|
||||
g.game_version,
|
||||
g.is_duplicate,
|
||||
g.release_year,
|
||||
g.purchase_price,
|
||||
g.estimated_value,
|
||||
g.condition_score,
|
||||
g.loaned_to,
|
||||
g.created_at,
|
||||
g.updated_at
|
||||
@@ -307,8 +327,12 @@ async function exportCatalogDumpWithClient(client) {
|
||||
title: row.title,
|
||||
genre: row.genre || "",
|
||||
publisher: row.publisher || "",
|
||||
version: row.game_version || "",
|
||||
isDuplicate: Boolean(row.is_duplicate),
|
||||
year: row.release_year || null,
|
||||
purchasePrice: row.purchase_price != null ? Number(row.purchase_price) : null,
|
||||
value: row.estimated_value != null ? Number(row.estimated_value) : null,
|
||||
condition: row.condition_score != null ? Number(row.condition_score) : null,
|
||||
loanedTo: row.loaned_to || "",
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
@@ -431,6 +455,7 @@ async function restoreCatalogDump(mode, dump) {
|
||||
const ensured = await ensureConsole(client, brand, consoleName);
|
||||
const consoleId = ensured.consoleId;
|
||||
const year = gameEntry && gameEntry.year != null && gameEntry.year !== "" ? Number(gameEntry.year) : null;
|
||||
const version = normalizeText(gameEntry && gameEntry.version);
|
||||
|
||||
const dedupeResult = await client.query(
|
||||
`
|
||||
@@ -439,9 +464,10 @@ async function restoreCatalogDump(mode, dump) {
|
||||
WHERE console_id = $1
|
||||
AND LOWER(title) = LOWER($2)
|
||||
AND COALESCE(release_year, 0) = COALESCE($3, 0)
|
||||
AND COALESCE(LOWER(game_version), '') = COALESCE(LOWER($4), '')
|
||||
LIMIT 1;
|
||||
`,
|
||||
[consoleId, title, year],
|
||||
[consoleId, title, year, version || null],
|
||||
);
|
||||
|
||||
if (dedupeResult.rowCount) {
|
||||
@@ -451,17 +477,38 @@ async function restoreCatalogDump(mode, dump) {
|
||||
const genre = normalizeText(gameEntry && gameEntry.genre) || null;
|
||||
const publisher = normalizeText(gameEntry && gameEntry.publisher) || null;
|
||||
const loanedTo = normalizeText(gameEntry && gameEntry.loanedTo) || null;
|
||||
const isDuplicate = Boolean(gameEntry && gameEntry.isDuplicate);
|
||||
const purchasePrice =
|
||||
gameEntry && gameEntry.purchasePrice != null && gameEntry.purchasePrice !== ""
|
||||
? Number(gameEntry.purchasePrice)
|
||||
: null;
|
||||
const value = gameEntry && gameEntry.value != null && gameEntry.value !== "" ? Number(gameEntry.value) : null;
|
||||
const condition =
|
||||
gameEntry && gameEntry.condition != null && gameEntry.condition !== "" ? Number(gameEntry.condition) : null;
|
||||
const createdAt = normalizeDateOrNull(gameEntry && gameEntry.createdAt);
|
||||
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO games(
|
||||
console_id, title, genre, publisher, release_year, estimated_value, loaned_to, created_at
|
||||
console_id, title, genre, publisher, game_version, is_duplicate, release_year, purchase_price,
|
||||
estimated_value, condition_score, loaned_to, created_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, COALESCE($8::timestamptz, NOW()));
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, COALESCE($12::timestamptz, NOW()));
|
||||
`,
|
||||
[consoleId, title, genre, publisher, year, value, loanedTo, createdAt],
|
||||
[
|
||||
consoleId,
|
||||
title,
|
||||
genre,
|
||||
publisher,
|
||||
version || null,
|
||||
isDuplicate,
|
||||
year,
|
||||
purchasePrice,
|
||||
value,
|
||||
condition,
|
||||
loanedTo,
|
||||
createdAt,
|
||||
],
|
||||
);
|
||||
|
||||
insertedGames += 1;
|
||||
@@ -510,19 +557,25 @@ async function createGame(payload) {
|
||||
|
||||
const genre = normalizeText(payload.genre) || null;
|
||||
const publisher = normalizeText(payload.publisher) || null;
|
||||
const version = normalizeText(payload.version) || null;
|
||||
const isDuplicate = Boolean(payload.isDuplicate);
|
||||
const loanedTo = normalizeText(payload.loanedTo) || null;
|
||||
const year = payload.year != null && payload.year !== "" ? Number(payload.year) : null;
|
||||
const purchasePrice =
|
||||
payload.purchasePrice != null && payload.purchasePrice !== "" ? Number(payload.purchasePrice) : null;
|
||||
const value = payload.value != null && payload.value !== "" ? Number(payload.value) : null;
|
||||
const condition = payload.condition != null && payload.condition !== "" ? Number(payload.condition) : null;
|
||||
|
||||
const insertResult = await client.query(
|
||||
`
|
||||
INSERT INTO games(
|
||||
console_id, title, genre, publisher, release_year, estimated_value, loaned_to
|
||||
console_id, title, genre, publisher, game_version, is_duplicate, release_year, purchase_price,
|
||||
estimated_value, condition_score, loaned_to
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING id::text AS id;
|
||||
`,
|
||||
[consoleData.consoleId, title, genre, publisher, year, value, loanedTo],
|
||||
[consoleData.consoleId, title, genre, publisher, version, isDuplicate, year, purchasePrice, value, condition, loanedTo],
|
||||
);
|
||||
|
||||
await client.query("COMMIT");
|
||||
@@ -548,9 +601,14 @@ async function updateGame(id, payload) {
|
||||
|
||||
const genre = normalizeText(payload.genre) || null;
|
||||
const publisher = normalizeText(payload.publisher) || null;
|
||||
const version = normalizeText(payload.version) || null;
|
||||
const isDuplicate = Boolean(payload.isDuplicate);
|
||||
const loanedTo = normalizeText(payload.loanedTo) || null;
|
||||
const year = payload.year != null && payload.year !== "" ? Number(payload.year) : null;
|
||||
const purchasePrice =
|
||||
payload.purchasePrice != null && payload.purchasePrice !== "" ? Number(payload.purchasePrice) : null;
|
||||
const value = payload.value != null && payload.value !== "" ? Number(payload.value) : null;
|
||||
const condition = payload.condition != null && payload.condition !== "" ? Number(payload.condition) : null;
|
||||
|
||||
const updateResult = await client.query(
|
||||
`
|
||||
@@ -560,13 +618,30 @@ async function updateGame(id, payload) {
|
||||
title = $3,
|
||||
genre = $4,
|
||||
publisher = $5,
|
||||
release_year = $6,
|
||||
estimated_value = $7,
|
||||
loaned_to = $8
|
||||
game_version = $6,
|
||||
is_duplicate = $7,
|
||||
release_year = $8,
|
||||
purchase_price = $9,
|
||||
estimated_value = $10,
|
||||
condition_score = $11,
|
||||
loaned_to = $12
|
||||
WHERE id = $1::uuid
|
||||
RETURNING id::text AS id;
|
||||
`,
|
||||
[id, consoleData.consoleId, title, genre, publisher, year, value, loanedTo],
|
||||
[
|
||||
id,
|
||||
consoleData.consoleId,
|
||||
title,
|
||||
genre,
|
||||
publisher,
|
||||
version,
|
||||
isDuplicate,
|
||||
year,
|
||||
purchasePrice,
|
||||
value,
|
||||
condition,
|
||||
loanedTo,
|
||||
],
|
||||
);
|
||||
|
||||
if (!updateResult.rowCount) {
|
||||
@@ -675,12 +750,17 @@ async function importCatalog(payload) {
|
||||
if (!title) {
|
||||
continue;
|
||||
}
|
||||
const version = normalizeText(game && game.version);
|
||||
const isDuplicate = Boolean(game && game.isDuplicate);
|
||||
|
||||
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 purchasePrice =
|
||||
game && game.purchasePrice != null && game.purchasePrice !== "" ? Number(game.purchasePrice) : null;
|
||||
const value = game && game.value != null && game.value !== "" ? Number(game.value) : null;
|
||||
const condition = game && game.condition != null && game.condition !== "" ? Number(game.condition) : null;
|
||||
|
||||
const dedupeResult = await client.query(
|
||||
`
|
||||
@@ -689,9 +769,10 @@ async function importCatalog(payload) {
|
||||
WHERE console_id = $1
|
||||
AND LOWER(title) = LOWER($2)
|
||||
AND COALESCE(release_year, 0) = COALESCE($3, 0)
|
||||
AND COALESCE(LOWER(game_version), '') = COALESCE(LOWER($4), '')
|
||||
LIMIT 1;
|
||||
`,
|
||||
[consoleId, title, year],
|
||||
[consoleId, title, year, version || null],
|
||||
);
|
||||
|
||||
if (dedupeResult.rowCount) {
|
||||
@@ -701,11 +782,12 @@ async function importCatalog(payload) {
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO games(
|
||||
console_id, title, genre, publisher, release_year, estimated_value, loaned_to
|
||||
console_id, title, genre, publisher, game_version, is_duplicate, release_year, purchase_price,
|
||||
estimated_value, condition_score, loaned_to
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7);
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
||||
`,
|
||||
[consoleId, title, genre, publisher, year, value, loanedTo],
|
||||
[consoleId, title, genre, publisher, version || null, isDuplicate, year, purchasePrice, value, condition, loanedTo],
|
||||
);
|
||||
|
||||
insertedGames += 1;
|
||||
|
||||
Reference in New Issue
Block a user