book/BookService.ts

Summary

Maintainability
C
7 hrs
Test Coverage
import * as fs from "fs"
import { CSVFileReader } from "../CSVFileReader"
import { FileUtil, Logger } from "ssg-api"
import { TimeContext } from "../time/TimeContext"
import { TimeUrlBuilder } from "../time/TimeUrlBuilder"
import * as path from "path"
import { StringUtil } from "../util/string/StringUtil"
import { Book } from "./Book"
import { RR0SsgContext, RR0SsgContextImpl } from "../RR0SsgContext"
import { KnownPeople, People } from "../people/People"
import { RR0FileUtil } from "../util/file/RR0FileUtil"
import { PeopleService } from "../people/PeopleService"

export class BookService {

  protected readonly intlOptions: Intl.DateTimeFormatOptions = {
    year: "numeric",
    month: "long",
    day: "numeric",
    weekday: "long",
    hour: "2-digit",
    minute: "2-digit"
  }

  protected peopleList: People[] = []

  constructor(readonly logger: Logger, protected dry: boolean, protected peopleService: PeopleService) {
  }

  async import(fileName: string) {
    const COLUMN_YEAR_PUBLISHED = "Year Published"
    const COLUMN_TITLE = "Title"
    const COLUMN_PUBLISHER = "Publisher"
    const COLUMN_AUTHOR = "Author"
    const COLUMN_AUTHOR_LAST_FIRST = "Author (Last, First)"
    const COLUMN_ISBN = "ISBN"
    const COLUMN_SUBTITLE = "Subtitle"
    const COLUMN_SERIES = "Series"
    const COLUMN_SUMMARY = "Summary"
    const columns = [COLUMN_TITLE, "Original Title", COLUMN_SUBTITLE, COLUMN_SERIES, "Volume", COLUMN_AUTHOR, COLUMN_AUTHOR_LAST_FIRST, COLUMN_PUBLISHER, COLUMN_YEAR_PUBLISHED, "Original Year Published", "Genre", COLUMN_SUMMARY, "Number of Pages", "Language", COLUMN_ISBN, "Rating", "Notes", "Google VolumeID", "Uploaded Image URL"]
    const readStream = fs.createReadStream(fileName)
    const csvSeparator = ","
    const reader = new CSVFileReader<Record<string, string>>(
      readStream,
      this.logger,
      columns,
      csvSeparator
    )
    const results = await reader.read()
    const books: Book[] = []
    for (const result of results) {
      const timeStr = result[COLUMN_YEAR_PUBLISHED]
      const title = result[COLUMN_TITLE]
      if (timeStr) {
        const year = parseInt(timeStr, 10)
        const time = new TimeContext(this.intlOptions, year)
        const author = result[COLUMN_AUTHOR]
        const summary = result[COLUMN_SUMMARY]
        const authorLastFirst = result[COLUMN_AUTHOR_LAST_FIRST]
        const authorsNames = author ? author.split(",") : [authorLastFirst]
        const authors: KnownPeople[] = []
        const context = new RR0SsgContextImpl("fr", new TimeContext(this.intlOptions))
        for (const authorName of authorsNames) {
          const authorFound = await this.findPeople(context, authorName)
          if (authorFound) {
            authors.push(authorFound)
          }
        }
        const publisher = result[COLUMN_PUBLISHER]
        const authorsLastNames = authors.map(author => author.lastName).join("-")
        const dirName = (authorsLastNames.length > 0 ? authorsLastNames : authorsNames.map(StringUtil.textToCamel))
          + "_" + StringUtil.capitalizeFirstLetter(StringUtil.textToCamel(title.toLowerCase()))
          + "_" + StringUtil.capitalizeFirstLetter(StringUtil.textToCamel(publisher))
        const parentDir = TimeUrlBuilder.fromTimeContext(time)
        const bookDir = path.join(parentDir, dirName)
        const book: Book = {
          title,
          authors: authorsNames,
          subTitle: result[COLUMN_SUBTITLE],
          series: result[COLUMN_SERIES],
          publication: {time, publisher},
          dirName: bookDir, summary,
          variants: [],
          isbn: result[COLUMN_ISBN]
        }
        const authorStr = authors?.map(author => author.dirName)
        if (fs.existsSync(bookDir)) {
          this.logger.log("Book directory", bookDir, "already exists, with authors", authorStr)
        } else {
          this.logger.log("Creating book directory", bookDir, "with authors", authorStr)
          if (!this.dry) {
            fs.mkdirSync(bookDir)
          }
        }
        const filePath = path.join(bookDir, `book.json`)
        const bookJson = JSON.stringify(book, null, 2)
        if (!this.dry) {
          await FileUtil.writeFile(filePath, bookJson, "utf-8")
        }
        books.push(book)
      } else {
        this.logger.warn(title, "has no", COLUMN_YEAR_PUBLISHED)
      }
    }
    return books
  }

  protected async findPeople(context: RR0SsgContext, fullName: string): Promise<KnownPeople | undefined> {
    if (this.peopleList.length <= 0) {
      const peopleDirectories = RR0FileUtil.findDirectoriesContaining("people*.json")
      this.peopleList = await this.peopleService.getFromDirs(context, peopleDirectories)
    }
    return this.peopleList.find(people => people.firstAndLastName === fullName)
  }
}