From a96961af8455c6a5a386020fd7b9f346b70b0ec8 Mon Sep 17 00:00:00 2001 From: godai78 Date: Tue, 2 Jun 2026 15:38:33 +0200 Subject: [PATCH 1/2] Add browse-only mode and basic login. --- add.html | 13 ++++- browse.html | 13 +++++ edit.html | 13 ++++- index.html | 136 ++++++++++++++++++++++++++++++++-------------- login.html | 131 ++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 20 +++++++ package.json | 1 + server.js | 47 ++++++++++++++-- 8 files changed, 327 insertions(+), 47 deletions(-) create mode 100644 browse.html create mode 100644 login.html diff --git a/add.html b/add.html index cc0c8dd..321a419 100644 --- a/add.html +++ b/add.html @@ -130,6 +130,11 @@

Add New Comic

+ + diff --git a/package-lock.json b/package-lock.json index 7b2382b..f7b63e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "comic-book-database", "version": "1.0.0", "dependencies": { + "cookie-parser": "^1.4.6", "csv-parse": "^6.2.1", "express": "^4.22.2", "node-fetch": "^2.7.0" @@ -124,6 +125,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", diff --git a/package.json b/package.json index b7ab6d3..3cfc24c 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "start": "node server.js" }, "dependencies": { + "cookie-parser": "^1.4.6", "csv-parse": "^6.2.1", "express": "^4.22.2", "node-fetch": "^2.7.0" diff --git a/server.js b/server.js index 8477b37..0844134 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,7 @@ const express = require('express'); const fs = require('fs').promises; const path = require('path'); +const cookieParser = require('cookie-parser'); const { importFromGoogleSheets } = require('./googleSheets'); const app = express(); @@ -9,9 +10,47 @@ let writeQueue = Promise.resolve(); // Middleware app.use(express.json()); +app.use(cookieParser()); app.use(express.static('.')); app.use('/translations', express.static('translations')); +const AUTH_USERNAME = process.env.AUTH_USER || 'admin'; +const AUTH_PASSWORD = process.env.AUTH_PASSWORD || 'password'; +const AUTH_TOKEN = process.env.AUTH_TOKEN || 'cbd-secret-token'; + +function getAuthToken(req) { + const authHeader = req.headers.authorization; + if (authHeader && authHeader.startsWith('Bearer ')) { + return authHeader.slice(7).trim(); + } + return req.cookies?.authToken || null; +} + +function requireAuth(req, res, next) { + const token = getAuthToken(req); + if (token !== AUTH_TOKEN) { + return res.status(401).json({ error: 'Unauthorized' }); + } + next(); +} + +app.post('/api/login', (req, res) => { + const { username, password } = req.body; + if (username === AUTH_USERNAME && password === AUTH_PASSWORD) { + res.cookie('authToken', AUTH_TOKEN, { + httpOnly: true, + sameSite: 'Lax' + }); + return res.json({ success: true, token: AUTH_TOKEN }); + } + res.status(401).json({ error: 'Invalid credentials' }); +}); + +app.post('/api/logout', (req, res) => { + res.clearCookie('authToken'); + res.json({ success: true }); +}); + // Data file path const dataFilePath = path.join(__dirname, 'comics.json'); @@ -127,7 +166,7 @@ app.get('/api/comics/:id', async (req, res) => { }); // Add a new comic -app.post('/api/comics', async (req, res) => { +app.post('/api/comics', requireAuth, async (req, res) => { try { const result = await withWriteLock(async () => { const comics = await readComics(); @@ -154,7 +193,7 @@ app.post('/api/comics', async (req, res) => { }); // Update a comic -app.put('/api/comics/:id', async (req, res) => { +app.put('/api/comics/:id', requireAuth, async (req, res) => { const id = parseInt(req.params.id); try { const result = await withWriteLock(async () => { @@ -195,7 +234,7 @@ app.put('/api/comics/:id', async (req, res) => { }); // Delete a comic -app.delete('/api/comics/:id', async (req, res) => { +app.delete('/api/comics/:id', requireAuth, async (req, res) => { const id = parseInt(req.params.id); try { const result = await withWriteLock(async () => { @@ -223,7 +262,7 @@ app.delete('/api/comics/:id', async (req, res) => { }); // Import comics from Google Sheets -app.post('/api/import', async (req, res) => { +app.post('/api/import', requireAuth, async (req, res) => { try { const sheetUrl = req.body?.url; if (!sheetUrl || typeof sheetUrl !== 'string') { From 019f4162723bd4a9924c8875109102888f83c34a Mon Sep 17 00:00:00 2001 From: godai78 Date: Tue, 2 Jun 2026 15:46:38 +0200 Subject: [PATCH 2/2] Translations update. --- CHANGELOG.md | 9 +++++++++ translations/de.json | 10 +++++----- translations/fr.json | 12 ++++++------ translations/pl.json | 10 +++++----- translations/sv.json | 12 ++++++------ 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a21829..2b62c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project are documented in this file. +## [0.7] - 2026-06-02 + +### Added +- Admin user login. +- Browsing without editing capabilities. + +### Changed +- Updated translations. + ## [0.6.1] - 2026-06-01 ### Changed diff --git a/translations/de.json b/translations/de.json index 098d229..30097a3 100644 --- a/translations/de.json +++ b/translations/de.json @@ -51,11 +51,11 @@ "error": "Ein Fehler ist aufgetreten", "loadingError": "Fehler beim Laden der Comics. Bitte versuchen Sie es erneut.", "import": { - "promptUrl": "Paste the shared Google Sheets URL:", - "errorPrefix": "Error importing comics:", - "noValidRows": "No valid comic rows found. Check sheet columns and ensure at least series or issue title is present.", - "notShared": "The spreadsheet is not publicly shared. Please share it with \"Anyone with the link can view\".", - "invalidGoogleUrl": "URL must be a docs.google.com Google Sheets link" + "promptUrl": "Fügen Sie die URL des freigegebenen Google Sheets-Dokuments ein:", + "errorPrefix": "Fehler beim Importieren der Comics:", + "noValidRows": "Keine gültigen Comic-Zeilen gefunden. Überprüfen Sie die Spalten des Tabellenblatts und stellen Sie sicher, dass mindestens der Serienname oder der Hefttitel vorhanden ist.", + "notShared": "Die Tabelle ist nicht öffentlich freigegeben. Bitte teilen Sie sie mit der Einstellung Jeder mit dem Link kann sie ansehen.", + "invalidGoogleUrl": "Die URL muss ein Google-Sheets-Link von docs.google.com sein" } }, "navigation": { diff --git a/translations/fr.json b/translations/fr.json index beef71a..50cca44 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -51,12 +51,12 @@ "error": "Une erreur s'est produite", "loadingError": "Erreur lors du chargement des BDs. Veuillez réessayer.", "import": { - "promptUrl": "Paste the shared Google Sheets URL:", - "errorPrefix": "Error importing comics:", - "noValidRows": "No valid comic rows found. Check sheet columns and ensure at least series or issue title is present.", - "notShared": "The spreadsheet is not publicly shared. Please share it with \"Anyone with the link can view\".", - "invalidGoogleUrl": "URL must be a docs.google.com Google Sheets link" - } + "promptUrl": "Collez l’URL du Google Sheets partagé :", + "errorPrefix": "Erreur lors de l’importation des bandes dessinées :", + "noValidRows": "Aucune ligne de bande dessinée valide trouvée. Vérifiez les colonnes de la feuille et assurez-vous qu’au moins le nom de la série ou le titre du numéro est présent.", + "notShared": "La feuille de calcul n’est pas partagée publiquement. Veuillez la partager avec l’option Toute personne disposant du lien peut afficher.", + "invalidGoogleUrl": "L’URL doit être un lien Google Sheets de docs.google.com" + } }, "navigation": { "backToMain": "← Retour à la page principale", diff --git a/translations/pl.json b/translations/pl.json index 0d7f447..76cc360 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -51,11 +51,11 @@ "error": "Wystąpił błąd", "loadingError": "Błąd podczas ładowania komiksów. Spróbuj ponownie.", "import": { - "promptUrl": "Paste the shared Google Sheets URL:", - "errorPrefix": "Error importing comics:", - "noValidRows": "No valid comic rows found. Check sheet columns and ensure at least series or issue title is present.", - "notShared": "The spreadsheet is not publicly shared. Please share it with \"Anyone with the link can view\".", - "invalidGoogleUrl": "URL must be a docs.google.com Google Sheets link" + "promptUrl": "Wklej URL udostępnionego arkusza Google Sheets:", + "errorPrefix": "Błąd podczas importowania komiksów:", + "noValidRows": "Nie znaleziono poprawnych wierszy komiksów. Sprawdź kolumny arkusza i upewnij się, że podano przynajmniej serię lub tytuł zeszytu.", + "notShared": "Arkusz nie jest publicznie udostępniony. Udostępnij go z opcją Każda osoba mająca link.", + "invalidGoogleUrl": "URL musi być linkiem do Google Sheets w domenie docs.google.com" } }, "navigation": { diff --git a/translations/sv.json b/translations/sv.json index 62fee95..c0cd905 100644 --- a/translations/sv.json +++ b/translations/sv.json @@ -51,12 +51,12 @@ "error": "Ett fel inträffade", "loadingError": "Fel vid laddning av serietidningar. Försök igen.", "import": { - "promptUrl": "Paste the shared Google Sheets URL:", - "errorPrefix": "Error importing comics:", - "noValidRows": "No valid comic rows found. Check sheet columns and ensure at least series or issue title is present.", - "notShared": "The spreadsheet is not publicly shared. Please share it with \"Anyone with the link can view\".", - "invalidGoogleUrl": "URL must be a docs.google.com Google Sheets link" - } + "promptUrl": "Klistra in URL:en till det delade Google Sheets-dokumentet:", + "errorPrefix": "Fel vid import av serier:", + "noValidRows": "Inga giltiga serierader hittades. Kontrollera kalkylarkets kolumner och se till att åtminstone serienamn eller nummerets titel finns angiven.", + "notShared": "Kalkylarket är inte offentligt delat. Dela det med inställningen Alla med länken kan visa.", + "invalidGoogleUrl": "URL:en måste vara en docs.google.com-länk till Google Sheets" + } }, "navigation": { "backToMain": "← Tillbaka till huvudsidan",