Add db loading from file to simplify dev

This commit is contained in:
pikiou 2022-01-30 01:51:01 +01:00
parent 110f3505ab
commit 65d9439308
3 changed files with 79 additions and 43 deletions

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ public/*
access/gsheets.json access/gsheets.json
access/jwt_secret.json access/jwt_secret.json
access/db.json access/db.json
access/dbToLoad.json
# Misc # Misc
.DS_Store .DS_Store

View File

@ -8,13 +8,16 @@ import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadshee
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json") const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
const DB_PATH = path.resolve(process.cwd(), "access/db.json") const DB_PATH = path.resolve(process.cwd(), "access/db.json")
const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
const REMOTE_UPDATE_DELAY = 40000 const REMOTE_UPDATE_DELAY = 40000
const DELAY_AFTER_QUERY = 2000 const DELAY_AFTER_QUERY = 2000
let creds: string | undefined | null let creds: string | undefined | null
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
let localDb: { [sheetName in keyof SheetNames]?: object[] | undefined } = {} let states: { [sheetName in keyof SheetNames]?: object[] | undefined } = {}
// eslint-disable-next-line @typescript-eslint/ban-types
let types: { [sheetName in keyof SheetNames]?: object | undefined } = {}
export type ElementWithId<ElementNoId> = { id: number } & ElementNoId export type ElementWithId<ElementNoId> = { id: number } & ElementNoId
@ -87,6 +90,8 @@ export class Sheet<
_state: Element[] | undefined _state: Element[] | undefined
_type: Record<keyof Element, string> | undefined
toRunAfterLoad: (() => void)[] | undefined = [] toRunAfterLoad: (() => void)[] | undefined = []
saveTimestamp = 0 saveTimestamp = 0
@ -155,7 +160,7 @@ export class Sheet<
} }
} }
runAfterLoad(func: () => void): void { addToRunAfterLoad(func: () => void): void {
if (this.toRunAfterLoad) { if (this.toRunAfterLoad) {
this.toRunAfterLoad.push(func) this.toRunAfterLoad.push(func)
} else { } else {
@ -165,15 +170,17 @@ export class Sheet<
private async waitForLoad(): Promise<void> { private async waitForLoad(): Promise<void> {
return new Promise((resolve, _reject) => { return new Promise((resolve, _reject) => {
this.runAfterLoad(() => resolve(undefined)) this.addToRunAfterLoad(() => resolve(undefined))
}) })
} }
dbUpdate(): void { async dbUpdate(): Promise<void> {
if (this.modifiedSinceSave) { if (await hasGSheetsAccess()) {
this.dbSave() if (this.modifiedSinceSave) {
} else { this.dbSave()
this.dbLoad() } else {
this.dbLoad()
}
} }
if (__DEV__) { if (__DEV__) {
this.localDbSave() this.localDbSave()
@ -181,11 +188,37 @@ export class Sheet<
} }
async localDbSave(): Promise<void> { async localDbSave(): Promise<void> {
localDb[this.name] = this._state states[this.name] = this._state
const jsonDB = __DEV__ ? JSON.stringify(localDb, null, 2) : JSON.stringify(localDb) types[this.name] = this._type
const toSave = { states, types }
const jsonDB = __DEV__ ? JSON.stringify(toSave, null, 2) : JSON.stringify(toSave)
await fs.writeFile(DB_PATH, jsonDB) await fs.writeFile(DB_PATH, jsonDB)
} }
async localDbLoad(): Promise<void> {
if (_.isEmpty(states)) {
let stringifiedDb
try {
stringifiedDb = await fs.readFile(DB_TO_LOAD_PATH)
} catch {
console.error(`No local database save found in ${DB_TO_LOAD_PATH}`)
process.exit()
}
if (stringifiedDb) {
const db = JSON.parse(stringifiedDb.toString())
states = db.states
types = db.types
}
}
if (!states[this.name]) {
console.error(`Sheet ${this.name} couldn't be found in localDb`)
process.exit()
}
this._state = states[this.name] as Element[]
this._type = types[this.name] as Record<keyof Element, string>
}
dbSave(): void { dbSave(): void {
this.saveTimestamp = +new Date() this.saveTimestamp = +new Date()
@ -197,40 +230,32 @@ export class Sheet<
} }
} }
dbLoad(): void { async dbLoad(): Promise<void> {
try { try {
this.toRunAfterLoad = [] if (await hasGSheetsAccess()) {
this.dbLoadAsync().then(() => { this.dbLoadAsync().then(() => this.doRunAfterLoad())
if (this.toRunAfterLoad) { } else {
this.toRunAfterLoad.map((func) => func()) this.doRunAfterLoad()
this.toRunAfterLoad = undefined }
}
})
} catch (e) { } catch (e) {
console.error("Error in dbLoad: ", e) console.error("Error in dbLoad: ", e)
} }
} }
async dbFirstLoad(): Promise<void> { doRunAfterLoad(): void {
if (await hasGSheetsAccess()) { if (this.toRunAfterLoad) {
this.dbLoad() this.toRunAfterLoad.map((func) => func())
} else if (_.isEmpty(localDb)) { this.toRunAfterLoad = undefined
let stringifiedDb
try {
stringifiedDb = await fs.readFile(DB_PATH)
} catch {
console.error(`Error: Found no DB file at ${DB_PATH}`)
}
if (stringifiedDb) {
localDb = JSON.parse(stringifiedDb.toString())
if (localDb[this.name]) {
this._state = localDb[this.name] as Element[]
}
}
this.dbLoad()
} }
} }
async dbFirstLoad(): Promise<void> {
if (!(await hasGSheetsAccess()) && _.isEmpty(states)) {
this.localDbLoad()
}
this.dbLoad()
}
private async dbSaveAsync(): Promise<void> { private async dbSaveAsync(): Promise<void> {
if (!this._state) { if (!this._state) {
return return
@ -249,7 +274,7 @@ export class Sheet<
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
const elements = this._state as Element[] const elements = this._state as Element[]
const types = _.pick(rows[0], Object.values(this.translation)) as Record< this._type = _.pick(rows[0], Object.values(this.translation)) as Record<
keyof Element, keyof Element,
string string
> >
@ -263,7 +288,7 @@ export class Sheet<
this.invertedTranslation, this.invertedTranslation,
(englishProp: string) => (element as any)[englishProp] (englishProp: string) => (element as any)[englishProp]
) as Element ) as Element
const stringifiedRow = this.stringifyElement(frenchElement, types) const stringifiedRow = this.stringifyElement(frenchElement, this._type)
if (!row) { if (!row) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
@ -317,17 +342,18 @@ export class Sheet<
if (!rows[0]) { if (!rows[0]) {
throw new Error(`No column types defined in sheet ${this.name}`) throw new Error(`No column types defined in sheet ${this.name}`)
} }
const types = _.pick(rows[0], Object.values(this.translation)) as Record< const typeList = _.pick(rows[0], Object.values(this.translation)) as Record<
keyof Element, keyof Element,
string string
> >
this._type = typeList
rows.shift() rows.shift()
rows.forEach((row) => { rows.forEach((row) => {
const stringifiedElement = _.pick(row, Object.values(this.translation)) as Record< const stringifiedElement = _.pick(row, Object.values(this.translation)) as Record<
keyof Element, keyof Element,
string string
> >
const frenchData: any = this.parseElement(stringifiedElement, types) const frenchData: any = this.parseElement(stringifiedElement, typeList)
if (frenchData !== undefined) { if (frenchData !== undefined) {
const englishElement = _.mapValues( const englishElement = _.mapValues(
this.translation, this.translation,
@ -361,10 +387,10 @@ export class Sheet<
private parseElement( private parseElement(
rawElement: Record<keyof Element, string>, rawElement: Record<keyof Element, string>,
types: Record<keyof Element, string> typeList: Record<keyof Element, string>
): Element { ): Element {
const fullElement = _.reduce( const fullElement = _.reduce(
types, typeList,
(element: any, type: string, prop: string) => { (element: any, type: string, prop: string) => {
const rawProp: string = rawElement[prop as keyof Element] const rawProp: string = rawElement[prop as keyof Element]
switch (type) { switch (type) {
@ -469,10 +495,10 @@ export class Sheet<
private stringifyElement( private stringifyElement(
element: Element, element: Element,
types: Record<keyof Element, string> typeList: Record<keyof Element, string>
): Record<keyof Element, string> { ): Record<keyof Element, string> {
const rawElement: Record<keyof Element, string> = _.reduce( const rawElement: Record<keyof Element, string> = _.reduce(
types, typeList,
(stringifiedElement: Record<keyof Element, string>, type: string, prop: string) => { (stringifiedElement: Record<keyof Element, string>, type: string, prop: string) => {
const value = element[prop as keyof Element] const value = element[prop as keyof Element]
switch (type) { switch (type) {

View File

@ -33,6 +33,7 @@ import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config" import config from "../config"
import notificationsSubscribe from "./notificationsSubscribe" import notificationsSubscribe from "./notificationsSubscribe"
import checkAccess from "./checkAccess" import checkAccess from "./checkAccess"
import { hasGSheetsAccess } from "./gsheets/accessors"
checkAccess() checkAccess()
@ -151,3 +152,11 @@ function onListening(server: any) {
const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}` const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}`
console.error(chalk.green(`\nServer listening on ${bind}`)) console.error(chalk.green(`\nServer listening on ${bind}`))
} }
hasGSheetsAccess().then((hasApiAccess: boolean) => {
if (hasApiAccess) {
console.error(chalk.green(`Database: remote Google Sheet`))
} else {
console.error(chalk.green(`Database: local db`))
}
})