diff --git a/backend/models/books.js b/backend/models/books.js index 4a40d87..fb1269b 100644 --- a/backend/models/books.js +++ b/backend/models/books.js @@ -2,7 +2,7 @@ const mongoose = require("@utils/mongoose"); const id = require("@utils/id"); const spineWidth = require("@utils/spine-width"); -const userSchema = mongoose.Schema({ +const schema = mongoose.Schema({ bookId: { type: String, default: () => id(), @@ -23,18 +23,18 @@ const userSchema = mongoose.Schema({ cover: { type: String }, coverColors: { type: Array }, ebook: { type: Object }, - pages: { type: Number, default: 0 }, + pages: { type: Number, default: 0, set: (v) => (Number.isNaN(v) ? 0 : v) }, hardback: { type: Boolean, default: false }, comments: [{ body: String, date: Date, user: String }], lastUpdated: { type: Date, type: Date, default: () => new Date() }, order: { type: Number, required: true, default: 0 }, }); -userSchema.virtual("width").get(function () { +schema.virtual("width").get(function () { return spineWidth(this.pages, this.hardback); }); -userSchema.set("toJSON", { virtuals: true }); -userSchema.set("toObject", { virtuals: true }); +schema.set("toJSON", { virtuals: true }); +schema.set("toObject", { virtuals: true }); -module.exports = mongoose.model("books", userSchema); +module.exports = mongoose.model("books", schema); diff --git a/backend/utils/fetch-retry.js b/backend/utils/fetch-retry.js new file mode 100644 index 0000000..6784256 --- /dev/null +++ b/backend/utils/fetch-retry.js @@ -0,0 +1,34 @@ +const logger = require("@utils/logger")(module); + +const fetchRetry = async ( + url, + options = {}, + { retries = 3, retryDelay = 500 } = {} +) => { + let lastError; + + for (let attempt = 0; attempt <= retries; attempt++) { + let response; + try { + response = await fetch(url, options); + logger.debug( + `Request attempt ${attempt + 1} for ${url} returned status ${response.status}` + ); + if (response.status >= 500 && response.status < 600) { + throw new Error(`Server error: ${response.status}`); + } + + return response; + } catch (err) { + lastError = err; + + if (attempt >= retries) { + return response; + } + + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + } + } +}; + +module.exports = fetchRetry; diff --git a/backend/utils/metadata-google.js b/backend/utils/metadata-google.js index 901d2e3..1c13495 100644 --- a/backend/utils/metadata-google.js +++ b/backend/utils/metadata-google.js @@ -2,6 +2,7 @@ const logger = require("@utils/logger")(module); const getImage = require("@utils/image-get"); +const fetchRetry = require("@utils/fetch-retry"); module.exports = async (isbn) => { let data = {}; @@ -9,14 +10,29 @@ module.exports = async (isbn) => { try { if (isbn) { - const response = await fetch( + const response = await fetchRetry( `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}` ); + if (!response.ok) { + logger.warn( + `Google books API request for ${isbn} returned status ${response.status}` + ); + return {}; + } data = await response.json(); + } else { + logger.warn(`Google books API request no ISBN provided`); + return {}; } - const book = data?.items[0]; + if (!data?.items || data?.items?.length === 0) { + logger.warn( + `Google books API request for ${isbn} returned no results` + ); + return {}; + } + const book = data?.items[0]; logger.info(`Google books API request for ${isbn}`); logger.debug(JSON.stringify(book, undefined, 4)); diff --git a/backend/utils/metadata-openlibrary.js b/backend/utils/metadata-openlibrary.js index 4b1fcd8..3172a64 100644 --- a/backend/utils/metadata-openlibrary.js +++ b/backend/utils/metadata-openlibrary.js @@ -2,6 +2,7 @@ const logger = require("@utils/logger")(module); const getImage = require("@utils/image-get"); +const fetchRetry = require("@utils/fetch-retry"); module.exports = async (isbn) => { let data = {}; @@ -10,10 +11,19 @@ module.exports = async (isbn) => { try { if (isbn) { - const response = await fetch( + const response = await fetchRetry( `https://openlibrary.org/isbn/${isbn}.json` ); + if (!response.ok) { + logger.warn( + `Open library API request for ${isbn} returned status ${response.status}` + ); + return {}; + } data = await response.json(); + } else { + logger.warn(`Open library API request no ISBN provided`); + return {}; } if (data?.covers) {