Skip to content

Commit

Permalink
feat(route): ✨ create BookShowHelper for all books/show.ts functi…
Browse files Browse the repository at this point in the history
…onality

This will allow strong unit testing to be written, and reduces complexity significantly
  • Loading branch information
djdembeck committed Aug 17, 2022
1 parent f220383 commit 4486643
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 62 deletions.
67 changes: 5 additions & 62 deletions src/config/routes/books/show.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { FastifyInstance } from 'fastify'

import type { BookDocument } from '#config/models/Book'
import { Book } from '#config/typing/books'
import { RequestGenericWithSeed } from '#config/typing/requests'
import SeedHelper from '#helpers/authors/audible/SeedHelper'
import StitchHelper from '#helpers/books/audible/StitchHelper'
import addTimestamps from '#helpers/database/addTimestamps'
import PaprAudibleBookHelper from '#helpers/database/audible/PaprAudibleBookHelper'
import RedisHelper from '#helpers/database/RedisHelper'
import BookShowHelper from '#helpers/routes/BookShowHelper'
import SharedHelper from '#helpers/shared'

async function _show(fastify: FastifyInstance) {
Expand All @@ -26,63 +20,12 @@ async function _show(fastify: FastifyInstance) {
throw new Error('Bad ASIN')
}

// Setup Helpers
const paprHelper = new PaprAudibleBookHelper(request.params.asin, options)
// Setup Helper
const { redis } = fastify
const redisHelper = new RedisHelper(redis, 'book', request.params.asin)
const helper = new BookShowHelper(request.params.asin, options, redis)

// Get book from database
const existingBook = (await paprHelper.findOne()).data
let book: Book | undefined = undefined

// Add dates to data if not present
if (existingBook && !existingBook.createdAt) {
paprHelper.bookData = addTimestamps(existingBook) as BookDocument
book = (await paprHelper.update()).data
} else {
const checkBook = await paprHelper.findOneWithProjection()
if (checkBook.data) {
book = checkBook.data
}
}

// Check for existing or cached data from redis
if (options.update !== '0') {
const redisBook = await redisHelper.findOrCreate(book)
if (redisBook) return redisBook
}

// Check if the object was updated recently
if (options.update == '0' && existingBook && commonHelpers.checkIfRecentlyUpdated(existingBook))
return book

// Proceed to stitch the book since it doesn't exist in db or is outdated
// Setup helper
const stitchHelper = new StitchHelper(request.params.asin)
// Request data to be processed by helper
const bookData = await stitchHelper.process()
// Pass requested data to the CRUD helper
paprHelper.bookData = bookData
// Let CRUD helper decide how to handle the data
const bookToReturn = await paprHelper.createOrUpdate()

// Throw error on null return data
if (!bookToReturn.data) {
throw new Error(`No data returned from database for book ${request.params.asin}`)
}

// Update Redis if the item is modified
if (bookToReturn.modified) {
redisHelper.setOne(bookToReturn.data)
}

// Seed authors in the background if it's a new/updated book
if (options.seedAuthors !== '0' && bookToReturn.modified) {
const authorSeeder = new SeedHelper(bookToReturn.data)
authorSeeder.seedAll()
}

return bookToReturn.data
// Call helper handler
return helper.handler()
})
}

Expand Down
132 changes: 132 additions & 0 deletions src/helpers/routes/BookShowHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { FastifyRedis } from '@fastify/redis'

import { BookDocument } from '#config/models/Book'
import { Book } from '#config/typing/books'
import { RequestGenericWithSeed } from '#config/typing/requests'
import SeedHelper from '#helpers/authors/audible/SeedHelper'
import StitchHelper from '#helpers/books/audible/StitchHelper'
import addTimestamps from '#helpers/database/addTimestamps'
import PaprAudibleBookHelper from '#helpers/database/audible/PaprAudibleBookHelper'
import RedisHelper from '#helpers/database/RedisHelper'
import SharedHelper from '#helpers/shared'

export default class BookShowHelper {
asin: string
bookInternal: Book | undefined = undefined
commonHelpers: SharedHelper
paprHelper: PaprAudibleBookHelper
redisHelper: RedisHelper
options: RequestGenericWithSeed['Querystring']
originalBook: BookDocument | null = null
stitchHelper: StitchHelper
constructor(asin: string, options: RequestGenericWithSeed['Querystring'], redis: FastifyRedis) {
this.asin = asin
this.commonHelpers = new SharedHelper()
this.options = options
this.paprHelper = new PaprAudibleBookHelper(this.asin, this.options)
this.redisHelper = new RedisHelper(redis, 'book', this.asin)
this.stitchHelper = new StitchHelper(this.asin)
}

async getBookFromPapr(): Promise<BookDocument | null> {
return (await this.paprHelper.findOne()).data
}

async getNewBookData() {
return this.stitchHelper.process()
}

async createOrUpdateBook() {
// Place the new book data into the papr helper
this.paprHelper.setBookData(await this.getNewBookData())
// Create or update the book
const bookToReturn = await this.paprHelper.createOrUpdate()
// Throw error on null return data
if (!bookToReturn.data) {
throw new Error(`An error occurred while updating book ${this.asin} in the DB`)
}
// Update or create the book in cache
await this.redisHelper.findOrCreate(bookToReturn.data)
// Return the book
return bookToReturn
}

/**
* Check if the book is updated recently by comparing the timestamps of updatedAt
*/
isUpdatedRecently() {
if (!this.originalBook) {
return false
}
return this.commonHelpers.checkIfRecentlyUpdated(this.originalBook)
}

/**
* Update the timestamps of the book when they are missing
*/
async updateBookTimestamps(): Promise<Book> {
// Return if not present or already has timestamps
if (!this.originalBook || this.originalBook.createdAt)
return (await this.paprHelper.findOneWithProjection()).data

// Add timestamps
this.paprHelper.bookData = addTimestamps(this.originalBook) as BookDocument
// Update book in DB
try {
this.bookInternal = (await this.paprHelper.update()).data
} catch (err) {
throw new Error(`An error occurred while adding timestamps to book ${this.asin} in the DB`)
}
return this.bookInternal
}

/**
* Actions to run when an update is requested
*/
async updateActions(): Promise<Book> {
// 1. Check if it is updated recently
if (this.isUpdatedRecently()) return this.originalBook as Book

// 2. Get the new book and create or update it
const bookToReturn = await this.createOrUpdateBook()

// 3. Update book in cache
if (bookToReturn.modified) {
this.redisHelper.setOne(bookToReturn.data)
}

// 4. Seed authors in the background
if (this.options.seedAuthors !== '0' && bookToReturn.modified) {
const authorSeeder = new SeedHelper(bookToReturn.data)
authorSeeder.seedAll()
}

// 5. Return the book
return bookToReturn.data
}

/**
* Main handler for the book show route
*/
async handler() {
this.originalBook = await this.getBookFromPapr()

// If the book is already present
if (this.originalBook) {
// If an update is requested
if (this.options.update === '1') {
return this.updateActions()
}
// 1. Make sure it has timestamps
await this.updateBookTimestamps()
// 2. Check it it is cached
const redisBook = await this.redisHelper.findOrCreate(this.originalBook)
if (redisBook) return redisBook as Book
// 3. Return the book from DB
return this.originalBook as Book
}

// If the book is not present
return (await this.createOrUpdateBook()).data
}
}

0 comments on commit 4486643

Please sign in to comment.