Source: utils/index.js

/**
 * @namespace Utilities
 */
import {firebase} from './firebase';

/**
* Label and its corresponding genre.
* @typedef Label - The label shown to the user
* @type {Object.<string, string>} 
* @property {string} genre - The genre associated with this label
*/

/**
* Genre and its associated label
* @typedef Genre - The genre of the book
* @type {Object.<string, string>} 
* @property {string} label - The label associated with this genre
*/

/**
 * Dictionary of labels and their associated genres
 * @constant
 * @type {Label}
 * @memberof Utilities
 * @default
 */
export const labels2Genre = {
  "Action and Adventure": "action",
  "Biographies": "biographies",
  "Classics": "classics",
  "Fantasy": "fantasy", 
  "Sci-Fi": "scifi", 
  "Suspense and Thrillers": "thrillers", 
  "Cookbooks": "cookbooks", 
  "Poetry": "poetry",
};


/**
 * Dictionary of genres and their associated labels
 * @constant
 * @type {Genre}
 * @memberof Utilities
 * @default
 */
export const genre2Labels = {
  "action": "Action and Adventure",
  "biographies": "Biographies",
  "classics": "Classics",
  "fantasy": "Fantasy", 
  "scifi": "Sci-Fi", 
  "thrillers": "Suspense and Thrillers", 
  "cookbooks": "Cookbooks", 
  "poetry": "Poetry",
};

/**
 * This function shuffles an array
 *
 * @function shuffle
 * @param {Array} array - The array to be shuffled
 * @memberof Utilities
 */
const shuffle = (array) => {
  return array.sort(() => Math.random() - 0.5);
}

/**
 * This function searches for a given book using the Google Books API and returns a book object.
 *
 * @function searchBookOnGoogle
 * @param {Object} bookToSearch - The book to search for
 * @param {string} bookToSearch.title - The target book title
 * @param {string} bookToSearch.author - The target book author
 * @param {boolean} verbose - Whether to log the found book
 * @returns {Object} The first result from the Google Books API
 * @memberof Utilities
*/
export const searchBookOnGoogle = async (bookToSearch, verbose = false) => {
  let response = await fetch(`https://www.googleapis.com/books/v1/volumes?q=${bookToSearch.title}${bookToSearch.author ? ('+inauthor:' + bookToSearch.author) : ''}`, { method: 'GET' });
  let json = await response.json();
  if (!json.items) return;
  let bookInfo = json.items[0].volumeInfo;
  let book = {
    title: bookInfo.title,
    key: bookInfo.title,
    author: bookInfo.authors?.length > 0 ? bookInfo.authors[0] : null,
    description: bookInfo.description,
    publisher: bookInfo.publisher,
    year: bookInfo.publishedDate,
    isbn: bookInfo.industryIdentifiers.find(el => el.type && el.type.startsWith('ISBN').identifier),
    coverUrl: bookInfo.imageLinks?.thumbnail
  };

  if (verbose) console.log(book);
  return book;
}

/**
 * This function gets the user's preferred genres from Firestore.
 *
 * @function getGenres
 * @returns {string[]} A list of genres.
 * @memberof Utilities
 */
export const getGenres = async () => {
  const uid = firebase.auth().currentUser.uid;
  const usersDoc = firebase.firestore().collection('users').doc(uid);
  const doc = await usersDoc.get();
  if (!doc.exists) {
    console.log('User does not exist, something\'s wrong');
    return;
  }
  const genres = doc.data().genres;
  return genres;
}

/**
 * This function gets books for given genres
 *
 * @function getBooksFromGenres
 * @param {Array} genres - The genres for which books are to be got
 * @param {number} [numOfBooks=1] - The number of books to return 
 * @memberof Utilities
 */
export const getBooksFromGenres = async (genres, numOfBooks = 1) => {
  const ret = await genres.reduce(async (acc, genre) => {
    return [...(await acc), ...(await getBooksFromGenre(genre, numOfBooks))];
  }, []);
  return ret;
}

/**
 * This function gets books for a given genre
 *
 * @function getBooksFromGenres
 * @param {string} genre - The genre for which books are to be got
 * @param {number} [numOfBooks=1] - The number of books to return 
 * @memberof Utilities
 */
export const getBooksFromGenre = async (genre, numOfBooks = 1) => {
  const booksRef = firebase.firestore().collection('books');
  const books = await booksRef.doc(genre).get();
  const random = shuffle(Object.values(books.data()));
  const selectedBooks = random.length > numOfBooks ? random.slice(0, numOfBooks) : random;
  return await Promise.all(selectedBooks.map(async book => await searchBookOnGoogle(book, true)));
}

/**
 * This function retrieves a user's reading list from Firestore.
 *
 * @function getReadingBooks
 * @returns {Object[]} The user's reading list.
 * @memberof Utilities
 */
export const getReadingBooks = async () => {
  const uid = firebase.auth().currentUser.uid;
  const usersDoc = firebase.firestore().collection('users').doc(uid);
  const doc = await usersDoc.get();
  if (!doc.exists) {
    console.log('User does not exist, something\'s wrong');
    return;
  }
  const reading = doc.data().reading;
  return reading;
}

/**
 * This function adds a book to a user's reading list on Firestore.
 *
 * @function addToReading
 * @param {Object} book - The book to add.
 * @memberof Utilities
 */
export const addToReading = async (book) => {
  const uid = firebase.auth().currentUser.uid;
  const usersDoc = firebase.firestore().collection('users').doc(uid);
  const doc = await usersDoc.get();
  if (!doc.exists) {
    console.log('User does not exist, something\'s wrong');
    return;
  }
  const reading = doc.data().reading;
  if (reading?.find(b => b && b.title == book.title) != undefined)
    return;
  console.log('user reading', reading);
  for (let key of Object.keys(book))
    book[key] == undefined && delete book[key];
  await usersDoc.update({
    reading: [
      ...(reading || []), book
    ]
  });
}

/**
 * This function removes a book from a user's reading list on Firestore.
 *
 * @function removeFromReading
 * @param {Object} book - The book to remove.
 * @memberof Utilities
 */
export const removeFromReading = async (book) => {
  const uid = firebase.auth().currentUser.uid;
  const usersDoc = firebase.firestore().collection('users').doc(uid);
  const doc = await usersDoc.get();
  if (!doc.exists) {
    console.log('User does not exist, something\'s wrong');
    return;
  }
  const reading = doc.data().reading;
  console.log(reading);
  const bookIdx = reading?.findIndex(b => b && b.title == book.title);
  console.log(bookIdx);
  if (bookIdx < 0)
    return;
  reading.splice(bookIdx, 1);
  
  await usersDoc.update({ reading: reading });
}

/**
 * This function adds a book to a user's preferred books on Firestore.
 *
 * @function addToPrefs
 * @param {Object} book - The book to add.
 * @memberof Utilities
 */
export const addToPrefs = async (book) => {
  const uid = firebase.auth().currentUser.uid;
  const usersDoc = firebase.firestore().collection('users').doc(uid);
  const doc = await usersDoc.get();
  if (!doc.exists) {
    console.log('User does not exist, something\'s wrong');
    return;
  }
  const prefs = doc.data().prefs;
  if (prefs?.find(b => b && b.title == book.title) != undefined)
    return;
  console.log('user prefs', prefs);
  for (let key of Object.keys(book))
    book[key] == undefined && delete book[key];
  await usersDoc.update({
    prefs: [
      ...(prefs || []), {
        title: book.title,
        author: book.author
      }
    ]
  });
}