compare versions
Compare veresions and update sets -> Cards
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
const { setAmountOwned } = require('../models/collectionModel');
|
||||||
|
|
||||||
|
async function updateAmountOwned(req, res) {
|
||||||
|
try {
|
||||||
|
const { card_id, set_id, rarity_id, amount_owned } = req.body;
|
||||||
|
|
||||||
|
if (
|
||||||
|
card_id == null ||
|
||||||
|
set_id == null ||
|
||||||
|
rarity_id == null ||
|
||||||
|
amount_owned == null
|
||||||
|
) {
|
||||||
|
return res.status(400).json({ error: 'Missing required fields' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount_owned < 0) {
|
||||||
|
return res.status(400).json({ error: 'Amount cannot be negative' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await setAmountOwned({
|
||||||
|
card_id,
|
||||||
|
set_id,
|
||||||
|
rarity_id,
|
||||||
|
amount_owned
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: 'Amount updated',
|
||||||
|
...result
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error updating amount:', err);
|
||||||
|
res.status(500).json({ error: 'Failed to update amount owned' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { updateAmountOwned };
|
||||||
+126
-134
@@ -1,172 +1,164 @@
|
|||||||
const {
|
const {upsertCard} = require('../models/cardModel');
|
||||||
upsertCard
|
const {upsertRarity,getRarityId} = require('../models/rarityModel');
|
||||||
} = require('../models/cardModel');
|
const { upsertSet } = require('../models/setModel');
|
||||||
const {
|
|
||||||
upsertRarity,
|
|
||||||
getRarityId
|
|
||||||
} = require('../models/rarityModel');
|
|
||||||
const { insertCardSetRarity } = require('../models/cardSetRarityModel');
|
const { insertCardSetRarity } = require('../models/cardSetRarityModel');
|
||||||
const { insertCardImage } = require('../models/cardImageModel');
|
const { insertCardImage } = require('../models/cardImageModel');
|
||||||
const { fetchAllCards } = require('../services/ygoproService');
|
const { fetchAllCards, fetchAllSets } = require('../services/ygoproService');
|
||||||
const db = require('../config/db');
|
const db = require('../config/db');
|
||||||
|
|
||||||
const BATCH_SIZE = 50;
|
const BATCH_SIZE = 50;
|
||||||
|
|
||||||
|
async function importSetsInternal() {
|
||||||
|
const sets = await fetchAllSets();
|
||||||
|
let added = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < sets.length; i += BATCH_SIZE) {
|
||||||
|
const batch = sets.slice(i, i + BATCH_SIZE);
|
||||||
|
await Promise.all(batch.map(async (set) => {
|
||||||
|
await upsertSet(set);
|
||||||
|
added++;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
added,
|
||||||
|
total: sets.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Express handler for /sets endpoint
|
||||||
async function importSets(req, res) {
|
async function importSets(req, res) {
|
||||||
try {
|
try {
|
||||||
const sets = await fetchAllSets();
|
const result = await importSetsInternal();
|
||||||
let added = 0;
|
res.json(result);
|
||||||
|
|
||||||
// Parallelized batch insert
|
|
||||||
for (let i = 0; i < sets.length; i += BATCH_SIZE) {
|
|
||||||
const batch = sets.slice(i, i + BATCH_SIZE);
|
|
||||||
await Promise.all(batch.map(async (set) => {
|
|
||||||
await upsertSet(set);
|
|
||||||
added++;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
added,
|
|
||||||
total: sets.length
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error importing sets:', err);
|
console.error('Error importing sets:', err);
|
||||||
res.status(500).json({ error: 'Failed to import sets' });
|
res.status(500).json({ error: 'Failed to import sets' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importCards(req, res) {
|
async function importCardsInternal() {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
const cards = await fetchAllCards();
|
||||||
|
const totalCards = cards.length;
|
||||||
|
|
||||||
try {
|
let addedCards = 0, addedImages = 0, addedRarities = 0;
|
||||||
const cards = await fetchAllCards();
|
|
||||||
const totalCards = cards.length;
|
|
||||||
|
|
||||||
let addedCards = 0, addedImages = 0, addedRarities = 0;
|
const rarityCache = {};
|
||||||
|
const setCache = {};
|
||||||
|
|
||||||
// Cache sets and rarities to reduce repeated DB lookups
|
const [setsRows] = await db.execute('SELECT id, set_name FROM sets');
|
||||||
const rarityCache = {};
|
for (const row of setsRows) {
|
||||||
const setCache = {};
|
setCache[row.set_name] = row.id;
|
||||||
|
}
|
||||||
|
|
||||||
// Preload sets into cache
|
const [rarityRows] = await db.execute('SELECT id, rarity_name FROM rarities');
|
||||||
const [setsRows] = await db.execute('SELECT id, set_code FROM sets');
|
for (const row of rarityRows) {
|
||||||
for (const row of setsRows) {
|
rarityCache[row.rarity_name] = row.id;
|
||||||
setCache[row.set_code] = row.id;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Preload rarities into cache
|
for (let i = 0; i < totalCards; i += BATCH_SIZE) {
|
||||||
const [rarityRows] = await db.execute('SELECT id, rarity_name FROM rarities');
|
const batch = cards.slice(i, i + BATCH_SIZE);
|
||||||
for (const row of rarityRows) {
|
const conn = await db.getConnection();
|
||||||
rarityCache[row.rarity_name] = row.id;
|
try {
|
||||||
}
|
await conn.beginTransaction();
|
||||||
|
|
||||||
// Process cards in batches
|
await Promise.all(batch.map(async (card) => {
|
||||||
for (let i = 0; i < totalCards; i += BATCH_SIZE) {
|
await conn.execute(`
|
||||||
const batch = cards.slice(i, i + BATCH_SIZE);
|
INSERT INTO cards
|
||||||
|
(id, name, card_type, frame_type, level, race, attribute, link_val, tcg_date, ocg_date)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
card_type = VALUES(card_type),
|
||||||
|
frame_type = VALUES(frame_type),
|
||||||
|
level = VALUES(level),
|
||||||
|
race = VALUES(race),
|
||||||
|
attribute = VALUES(attribute),
|
||||||
|
link_val = VALUES(link_val),
|
||||||
|
tcg_date = VALUES(tcg_date),
|
||||||
|
ocg_date = VALUES(ocg_date)
|
||||||
|
`, [
|
||||||
|
card.id,
|
||||||
|
card.name,
|
||||||
|
card.type,
|
||||||
|
card.frameType,
|
||||||
|
card.level || null,
|
||||||
|
card.race || null,
|
||||||
|
card.attribute || null,
|
||||||
|
card.linkval || null,
|
||||||
|
card.tcg_date || null,
|
||||||
|
card.ocg_date || null
|
||||||
|
]);
|
||||||
|
addedCards++;
|
||||||
|
|
||||||
// Wrap batch in a transaction
|
if (Array.isArray(card.card_sets)) {
|
||||||
const conn = await db.getConnection();
|
for (const set of card.card_sets) {
|
||||||
try {
|
if (!rarityCache[set.set_rarity]) {
|
||||||
await conn.beginTransaction();
|
|
||||||
|
|
||||||
await Promise.all(batch.map(async (card) => {
|
|
||||||
// Upsert card
|
|
||||||
await conn.execute(`
|
|
||||||
INSERT INTO cards
|
|
||||||
(id, name, card_type, frame_type, level, race, attribute, link_val, tcg_date, ocg_date)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
name = VALUES(name),
|
|
||||||
card_type = VALUES(card_type),
|
|
||||||
frame_type = VALUES(frame_type),
|
|
||||||
level = VALUES(level),
|
|
||||||
race = VALUES(race),
|
|
||||||
attribute = VALUES(attribute),
|
|
||||||
link_val = VALUES(link_val),
|
|
||||||
tcg_date = VALUES(tcg_date),
|
|
||||||
ocg_date = VALUES(ocg_date)
|
|
||||||
`, [
|
|
||||||
card.id,
|
|
||||||
card.name,
|
|
||||||
card.type,
|
|
||||||
card.frameType,
|
|
||||||
card.level || null,
|
|
||||||
card.race || null,
|
|
||||||
card.attribute || null,
|
|
||||||
card.linkval || null,
|
|
||||||
card.tcg_date || null,
|
|
||||||
card.ocg_date || null
|
|
||||||
]);
|
|
||||||
addedCards++;
|
|
||||||
|
|
||||||
// Process rarities and card_sets_rarity
|
|
||||||
if (Array.isArray(card.card_sets)) {
|
|
||||||
for (const set of card.card_sets) {
|
|
||||||
// Upsert rarity if not cached
|
|
||||||
if (!rarityCache[set.set_rarity]) {
|
|
||||||
await conn.execute(`
|
|
||||||
INSERT INTO rarities (rarity_name, rarity_code)
|
|
||||||
VALUES (?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE rarity_code = VALUES(rarity_code)
|
|
||||||
`, [set.set_rarity, set.set_rarity_code]);
|
|
||||||
const [rows] = await conn.execute('SELECT id FROM rarities WHERE rarity_name = ? LIMIT 1', [set.set_rarity]);
|
|
||||||
rarityCache[set.set_rarity] = rows[0].id;
|
|
||||||
addedRarities++;
|
|
||||||
}
|
|
||||||
const rarity_id = rarityCache[set.set_rarity];
|
|
||||||
|
|
||||||
// Get set_id from cache
|
|
||||||
const set_id = setCache[set.set_code];
|
|
||||||
if (set_id) {
|
|
||||||
await conn.execute(`
|
|
||||||
INSERT INTO card_sets_rarity (card_id, set_id, rarity_id, card_set_code)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE card_set_code = VALUES(card_set_code)
|
|
||||||
`, [card.id, set_id, rarity_id, set.set_code]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process card images
|
|
||||||
if (Array.isArray(card.card_images)) {
|
|
||||||
for (const img of card.card_images) {
|
|
||||||
await conn.execute(`
|
await conn.execute(`
|
||||||
INSERT INTO card_images (card_id, image_url)
|
INSERT INTO rarities (rarity_name, rarity_code)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
`, [card.id, img.image_url]);
|
ON DUPLICATE KEY UPDATE rarity_code = VALUES(rarity_code)
|
||||||
addedImages++;
|
`, [set.set_rarity, set.set_rarity_code]);
|
||||||
|
const [rows] = await conn.execute('SELECT id FROM rarities WHERE rarity_name = ? LIMIT 1', [set.set_rarity]);
|
||||||
|
rarityCache[set.set_rarity] = rows[0].id;
|
||||||
|
addedRarities++;
|
||||||
|
}
|
||||||
|
const rarity_id = rarityCache[set.set_rarity];
|
||||||
|
const set_id = setCache[set.set_name];
|
||||||
|
if (set_id) {
|
||||||
|
await conn.execute(`
|
||||||
|
INSERT INTO card_sets_rarity (card_id, set_id, rarity_id, card_set_code)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE card_set_code = VALUES(card_set_code)
|
||||||
|
`, [card.id, set_id, rarity_id, set.set_code]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}
|
||||||
|
|
||||||
await conn.commit();
|
if (Array.isArray(card.card_images)) {
|
||||||
} catch (err) {
|
for (const img of card.card_images) {
|
||||||
await conn.rollback();
|
await conn.execute(`
|
||||||
throw err;
|
INSERT INTO card_images (card_id, image_url)
|
||||||
} finally {
|
VALUES (?, ?)
|
||||||
conn.release();
|
`, [card.id, img.image_url]);
|
||||||
}
|
addedImages++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// Release batch memory
|
await conn.commit();
|
||||||
batch.length = 0;
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
const endTime = Date.now();
|
batch.length = 0;
|
||||||
const durationSeconds = ((endTime - startTime) / 1000).toFixed(2);
|
}
|
||||||
|
|
||||||
res.json({
|
const durationSeconds = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||||
total_cards: totalCards,
|
|
||||||
cards_added: addedCards,
|
|
||||||
images_added: addedImages,
|
|
||||||
rarities_added: addedRarities,
|
|
||||||
duration_seconds: parseFloat(durationSeconds)
|
|
||||||
});
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
total_cards: totalCards,
|
||||||
|
cards_added: addedCards,
|
||||||
|
images_added: addedImages,
|
||||||
|
rarities_added: addedRarities,
|
||||||
|
duration_seconds: parseFloat(durationSeconds)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Express handler for /cards
|
||||||
|
async function importCards(req, res) {
|
||||||
|
try {
|
||||||
|
const result = await importCardsInternal();
|
||||||
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error importing cards:', err);
|
console.error('Error importing cards:', err);
|
||||||
res.status(500).json({ error: 'Failed to import cards' });
|
res.status(500).json({ error: 'Failed to import cards' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { importCards, importSets };
|
|
||||||
|
module.exports = { importCards, importCardsInternal, importSets, importSetsInternal };
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
const { fetchDatabaseVersion } = require('../services/ygoproService');
|
||||||
|
const { getLocalDBVersion, setLocalDBVersion } = require('../models/dbVersionModel');
|
||||||
|
const { importSetsInternal } = require('./importController');
|
||||||
|
const { importCardsInternal } = require('./importController');
|
||||||
|
|
||||||
|
async function importFullDatabase(req, res) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const remoteVersion = await fetchDatabaseVersion();
|
||||||
|
const localVersion = await getLocalDBVersion();
|
||||||
|
|
||||||
|
if (localVersion && localVersion.database_version === remoteVersion.database_version) {
|
||||||
|
return res.json({
|
||||||
|
message: 'Database is already up to date.',
|
||||||
|
version: remoteVersion.database_version
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const setsStats = await importSetsInternal();
|
||||||
|
|
||||||
|
const cardsStats = await importCardsInternal();
|
||||||
|
|
||||||
|
await setLocalDBVersion(remoteVersion.database_version, remoteVersion.last_update);
|
||||||
|
|
||||||
|
const totalDuration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: 'Database import completed successfully.',
|
||||||
|
version: remoteVersion.database_version,
|
||||||
|
sets: setsStats,
|
||||||
|
cards: cardsStats,
|
||||||
|
duration_seconds: parseFloat(totalDuration)
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in full database import:', err);
|
||||||
|
res.status(500).json({ error: 'Failed to import database' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { importFullDatabase };
|
||||||
@@ -11,3 +11,6 @@ const PORT = process.env.PORT || 3000;
|
|||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server running on http://localhost:${PORT}`);
|
console.log(`Server running on http://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const collectionRoutes = require('./routes/collectionRoutes');
|
||||||
|
app.use('/collection', collectionRoutes);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
const db = require('../config/db');
|
||||||
|
|
||||||
|
async function setAmountOwned({ card_id, set_id, rarity_id, amount_owned }) {
|
||||||
|
await db.execute(`
|
||||||
|
INSERT INTO card_sets_rarity (card_id, set_id, rarity_id, amount_owned)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
amount_owned = VALUES(amount_owned)
|
||||||
|
`, [card_id, set_id, rarity_id, amount_owned]);
|
||||||
|
|
||||||
|
return { card_id, set_id, rarity_id, amount_owned };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { setAmountOwned };
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
const db = require('../config/db');
|
||||||
|
|
||||||
|
async function getLocalDBVersion() {
|
||||||
|
const [rows] = await db.execute(
|
||||||
|
'SELECT database_version, last_update FROM db_version ORDER BY last_update DESC LIMIT 1'
|
||||||
|
);
|
||||||
|
return rows.length ? rows[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLocalDBVersion(version, date) {
|
||||||
|
await db.execute(
|
||||||
|
'INSERT INTO db_version (database_version, last_update) VALUES (?, ?)',
|
||||||
|
[version ?? null, date ?? null]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getLocalDBVersion, setLocalDBVersion };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { updateAmountOwned } = require('../controllers/collectionController');
|
||||||
|
|
||||||
|
router.put('/amount', updateAmountOwned);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { importSets, importCards} = require('../controllers/importController');
|
const { importSets, importCards} = require('../controllers/importController');
|
||||||
|
const { importFullDatabase } = require('../controllers/newVersionController');
|
||||||
|
|
||||||
router.post('/sets', importSets);
|
router.post('/sets', importSets);
|
||||||
router.post('/cards', importCards);
|
router.post('/cards', importCards);
|
||||||
|
router.post('/full-import', importFullDatabase);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -22,12 +22,29 @@ async function fetchAllSets() {
|
|||||||
async function fetchAllCards() {
|
async function fetchAllCards() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_BASE}/cardinfo.php?misc=yes`);
|
const response = await axios.get(`${API_BASE}/cardinfo.php?misc=yes`);
|
||||||
return response.data.data; // array of card objects
|
return response.data.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching cards from YGOPRODeck:', err.message);
|
console.error('Error fetching cards from YGOPRODeck:', err.message);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchDatabaseVersion() {
|
||||||
|
const response = await axios.get(`${API_BASE}/checkDBVer.php`);
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
module.exports = { fetchAllCards, fetchAllSets };
|
// Handle array response from API
|
||||||
|
const versionInfo = Array.isArray(data) ? data[0] : data;
|
||||||
|
|
||||||
|
if (!versionInfo || !versionInfo.database_version || !versionInfo.last_update) {
|
||||||
|
throw new Error('Invalid database version response from YGOPRODeck API');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
database_version: versionInfo.database_version,
|
||||||
|
last_update: versionInfo.last_update
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { fetchAllCards, fetchAllSets, fetchDatabaseVersion };
|
||||||
Reference in New Issue
Block a user