diff --git a/docker-compose.yml b/docker-compose.yml index 2c8bee8..3ff09d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,9 @@ services: environment: - PORT=3333 - ADMIN_PASSWORD=changeme + - DB_PATH=/app/data/colony.json + - DECKS_PATH=/app/data/decks.json + - DECK_IMAGES_PATH=/app/data/deck-images volumes: - - ./data/colony.json:/app/colony.json - - ./data/decks.json:/app/decks.json - - ./data/deck-images:/app/assets/deck-images + - ./data:/app/data restart: unless-stopped diff --git a/entrypoint.sh b/entrypoint.sh index 2f361fd..1c9695a 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,17 +1,6 @@ #!/bin/sh set -e - -# If Docker created these paths as directories (because the host file didn't exist), -# remove them and create proper JSON files. -for f in /app/colony.json /app/decks.json; do - if [ -d "$f" ]; then - rm -rf "$f" - fi - if [ ! -f "$f" ]; then - echo '{}' > "$f" - fi -done - -mkdir -p /app/assets/deck-images - +mkdir -p /app/data /app/assets/deck-images +[ -f /app/data/colony.json ] || echo '{"games":[]}' > /app/data/colony.json +[ -f /app/data/decks.json ] || echo '{"decks":[]}' > /app/data/decks.json exec node server.js diff --git a/server.js b/server.js index e188c95..fb26d4c 100644 --- a/server.js +++ b/server.js @@ -38,7 +38,9 @@ function broadcast(slug) { try { res.write(msg) } catch {} } } -const DB_PATH = path.join(__dirname, 'colony.json') +const DB_PATH = process.env.DB_PATH || path.join(__dirname, 'colony.json') +const DECKS_PATH = process.env.DECKS_PATH || path.join(__dirname, 'decks.json') +const DECK_IMAGES_PATH = process.env.DECK_IMAGES_PATH || path.join(__dirname, 'assets', 'deck-images') function loadDB() { try { return JSON.parse(fs.readFileSync(DB_PATH, 'utf8')) } @@ -289,13 +291,13 @@ app.delete('/api/games/:slug/keywords/:id', requirePassword, (req, res) => { // --- Decks (image libraries) --- function loadDecks() { - try { return JSON.parse(fs.readFileSync(path.join(__dirname, 'decks.json'), 'utf8')) } + try { return JSON.parse(fs.readFileSync(DECKS_PATH, 'utf8')) } catch { return { decks: [] } } } function saveDecks(data) { - const tmp = path.join(__dirname, 'decks.json') + '.tmp' + const tmp = DECKS_PATH + '.tmp' fs.writeFileSync(tmp, JSON.stringify(data, null, 2)) - fs.renameSync(tmp, path.join(__dirname, 'decks.json')) + fs.renameSync(tmp, DECKS_PATH) } app.get('/api/decks', (req, res) => { @@ -328,7 +330,7 @@ app.delete('/api/decks/:id', requireAdmin, (req, res) => { if (idx === -1) return res.status(404).json({ error: 'Deck not found' }) // Delete image files for (const img of db.decks[idx].images || []) { - try { fs.unlinkSync(path.join(__dirname, 'assets', 'deck-images', img.filename)) } catch {} + try { fs.unlinkSync(path.join(DECK_IMAGES_PATH, img.filename)) } catch {} } db.decks.splice(idx, 1) saveDecks(db) @@ -368,7 +370,7 @@ app.delete('/api/decks/:deckId/cards/:cardId', requireAdmin, (req, res) => { const idx = (deck.cards || []).findIndex(c => c.id === req.params.cardId) if (idx === -1) return res.status(404).json({ error: 'Card not found' }) for (const img of deck.cards[idx].images || []) { - try { fs.unlinkSync(path.join(__dirname, 'assets', 'deck-images', img.filename)) } catch {} + try { fs.unlinkSync(path.join(DECK_IMAGES_PATH, img.filename)) } catch {} } deck.cards.splice(idx, 1) saveDecks(db) @@ -385,8 +387,8 @@ app.post('/api/decks/:deckId/cards/:cardId/images', requireAdmin, (req, res) => if (!data) return res.status(400).json({ error: 'data required' }) const ext = path.extname(filename || '.jpg') || '.jpg' const savedName = `deck-${newId()}${ext}` - fs.mkdirSync(path.join(__dirname, 'assets', 'deck-images'), { recursive: true }) - fs.writeFileSync(path.join(__dirname, 'assets', 'deck-images', savedName), Buffer.from(data, 'base64')) + fs.mkdirSync(DECK_IMAGES_PATH, { recursive: true }) + fs.writeFileSync(path.join(DECK_IMAGES_PATH, savedName), Buffer.from(data, 'base64')) const img = { id: newId(), filename: savedName, keywords: keywords || [], created_at: now() } card.images = card.images || [] card.images.push(img) @@ -402,7 +404,7 @@ app.delete('/api/decks/:deckId/cards/:cardId/images/:imgId', requireAdmin, (req, if (!card) return res.status(404).json({ error: 'Card not found' }) const idx = (card.images || []).findIndex(i => i.id === req.params.imgId) if (idx === -1) return res.status(404).json({ error: 'Image not found' }) - try { fs.unlinkSync(path.join(__dirname, 'assets', 'deck-images', card.images[idx].filename)) } catch {} + try { fs.unlinkSync(path.join(DECK_IMAGES_PATH, card.images[idx].filename)) } catch {} card.images.splice(idx, 1) saveDecks(db) res.json({ ok: true }) @@ -417,7 +419,7 @@ app.post('/api/decks/:id/images', requireAdmin, (req, res) => { if (!data) return res.status(400).json({ error: 'data required' }) const ext = path.extname(filename || '.jpg') || '.jpg' const savedName = `deck-${newId()}${ext}` - const imgPath = path.join(__dirname, 'assets', 'deck-images', savedName) + const imgPath = path.join(DECK_IMAGES_PATH, savedName) fs.mkdirSync(path.dirname(imgPath), { recursive: true }) fs.writeFileSync(imgPath, Buffer.from(data, 'base64')) const img = { id: newId(), filename: savedName, keywords: keywords || [], created_at: now() } @@ -444,7 +446,7 @@ app.delete('/api/decks/:deckId/images/:imgId', requireAdmin, (req, res) => { if (!deck) return res.status(404).json({ error: 'Deck not found' }) const idx = deck.images.findIndex(i => i.id === req.params.imgId) if (idx === -1) return res.status(404).json({ error: 'Image not found' }) - try { fs.unlinkSync(path.join(__dirname, 'assets', 'deck-images', deck.images[idx].filename)) } catch {} + try { fs.unlinkSync(path.join(DECK_IMAGES_PATH, deck.images[idx].filename)) } catch {} deck.images.splice(idx, 1) saveDecks(db) res.json({ ok: true })