From db3dba7a40836a8c69da6dbd1d40f46291e83621 Mon Sep 17 00:00:00 2001 From: forceoranj Date: Tue, 16 Nov 2021 16:33:01 +0100 Subject: [PATCH] Makes all gSheet calls sequential --- src/gsheets/accessors.ts | 208 ++++++++++++++------------ src/gsheets/sequentialDBOperations.ts | 30 ++++ 2 files changed, 142 insertions(+), 96 deletions(-) create mode 100644 src/gsheets/sequentialDBOperations.ts diff --git a/src/gsheets/accessors.ts b/src/gsheets/accessors.ts index c09534d..b2137b1 100644 --- a/src/gsheets/accessors.ts +++ b/src/gsheets/accessors.ts @@ -3,6 +3,10 @@ import _ from "lodash" import { promises as fs } from "fs" import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadsheet" +import sequentialDBOperations from "./sequentialDBOperations" + +const addDBOperation = sequentialDBOperations() + const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"] const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json") @@ -13,28 +17,30 @@ export async function listGet( specimen: Element ): Promise { type StringifiedElement = Record - const sheet = await getGSheet(sheetName) + return addDBOperation(async () => { + const sheet = await getGSheet(sheetName) - // Load sheet into an array of objects - const rows = (await sheet.getRows()) as StringifiedElement[] - const elements: Element[] = [] - if (!rows[0]) { - throw new Error(`No column types defined in sheet ${sheetName}`) - } - const types = _.pick(rows[0], Object.keys(specimen)) as Record - rows.shift() - rows.forEach((row) => { - const stringifiedElement = _.pick(row, Object.keys(specimen)) as Record< - keyof Element, - string - > - const element = parseElement(stringifiedElement, types, specimen) - if (element !== undefined) { - elements.push(element) + // Load sheet into an array of objects + const rows = (await sheet.getRows()) as StringifiedElement[] + const elements: Element[] = [] + if (!rows[0]) { + throw new Error(`No column types defined in sheet ${sheetName}`) } - }) + const types = _.pick(rows[0], Object.keys(specimen)) as Record + rows.shift() + rows.forEach((row) => { + const stringifiedElement = _.pick(row, Object.keys(specimen)) as Record< + keyof Element, + string + > + const element = parseElement(stringifiedElement, types, specimen) + if (element !== undefined) { + elements.push(element) + } + }) - return elements + return elements + }) } export async function get( @@ -42,6 +48,7 @@ export async function get( membreId: number, specimen: Element ): Promise { + // No need to addDBOperation here, since listGet does it already const list = await listGet(sheetName, specimen) return list.find((element) => element.id === membreId) } @@ -50,52 +57,57 @@ export async function setList( sheetName: string, elements: Element[] ): Promise { - const sheet = await getGSheet(sheetName) + return addDBOperation(async () => { + const sheet = await getGSheet(sheetName) - // Load sheet into an array of objects - const rows = await sheet.getRows() - if (!rows[0]) { - throw new Error(`No column types defined in sheet ${sheetName}`) - } - const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record + // Load sheet into an array of objects + const rows = await sheet.getRows() + if (!rows[0]) { + throw new Error(`No column types defined in sheet ${sheetName}`) + } + const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record< + keyof Element, + string + > - // Update received rows - let rowid = 1 - // eslint-disable-next-line no-restricted-syntax - for (const element of elements) { - const row = rows[rowid] - const stringifiedRow = stringifyElement(element, types) + // Update received rows + let rowid = 1 + // eslint-disable-next-line no-restricted-syntax + for (const element of elements) { + const row = rows[rowid] + const stringifiedRow = stringifyElement(element, types) - if (!row) { - // eslint-disable-next-line no-await-in-loop - await sheet.addRow(stringifiedRow) - } else { - const keys = Object.keys(stringifiedRow) - const sameCells = _.every( - keys, - (key: keyof Element) => row[key as string] === stringifiedRow[key] - ) - if (!sameCells) { - keys.forEach((key) => { - row[key] = stringifiedRow[key as keyof Element] - }) + if (!row) { // eslint-disable-next-line no-await-in-loop - await row.save() + await sheet.addRow(stringifiedRow) + } else { + const keys = Object.keys(stringifiedRow) + const sameCells = _.every( + keys, + (key: keyof Element) => row[key as string] === stringifiedRow[key] + ) + if (!sameCells) { + keys.forEach((key) => { + row[key] = stringifiedRow[key as keyof Element] + }) + // eslint-disable-next-line no-await-in-loop + await row.save() + } + } + + rowid += 1 + } + + // Delete all following rows + for (let rowToDelete = sheet.rowCount - 1; rowToDelete >= rowid; rowToDelete -= 1) { + if (rows[rowToDelete]) { + // eslint-disable-next-line no-await-in-loop + await rows[rowToDelete].delete() } } - rowid += 1 - } - - // Delete all following rows - for (let rowToDelete = sheet.rowCount - 1; rowToDelete >= rowid; rowToDelete -= 1) { - if (rows[rowToDelete]) { - // eslint-disable-next-line no-await-in-loop - await rows[rowToDelete].delete() - } - } - - return true + return true + }) } export async function set( @@ -105,25 +117,27 @@ export async function set( if (!element) { return undefined } - const sheet = await getGSheet(sheetName) + return addDBOperation(async () => { + const sheet = await getGSheet(sheetName) - // Load sheet into an array of objects - const rows = await sheet.getRows() - if (!rows[0]) { - throw new Error(`No column types defined in sheet ${sheetName}`) - } - const types = _.pick(rows[0], Object.keys(element || {})) as Record - rows.shift() + // Load sheet into an array of objects + const rows = await sheet.getRows() + if (!rows[0]) { + throw new Error(`No column types defined in sheet ${sheetName}`) + } + const types = _.pick(rows[0], Object.keys(element || {})) as Record + rows.shift() - // Replace previous row - const stringifiedRow = stringifyElement(element, types) - const row = rows.find((rowItem) => +rowItem.id === element.id) - if (!row) { - return undefined - } - Object.assign(row, stringifiedRow) - await row.save() - return element + // Replace previous row + const stringifiedRow = stringifyElement(element, types) + const row = rows.find((rowItem) => +rowItem.id === element.id) + if (!row) { + return undefined + } + Object.assign(row, stringifiedRow) + await row.save() + return element + }) } // eslint-disable-next-line @typescript-eslint/ban-types @@ -134,31 +148,33 @@ export async function add { + const sheet = await getGSheet(sheetName) - // Load sheet into an array of objects - const rows = await sheet.getRows() - if (!rows[0]) { - throw new Error(`No column types defined in sheet ${sheetName}`) - } - const types = { - id: "number", - ...(_.pick(rows[0], Object.keys(partialElement || {})) as Record< - keyof ElementNoId, - string - >), - } + // Load sheet into an array of objects + const rows = await sheet.getRows() + if (!rows[0]) { + throw new Error(`No column types defined in sheet ${sheetName}`) + } + const types = { + id: "number", + ...(_.pick(rows[0], Object.keys(partialElement || {})) as Record< + keyof ElementNoId, + string + >), + } - // Create full element - rows.shift() - const highestId = rows.reduce((id: number, row) => Math.max(id, +row.id || 0), 0) - const element = { id: highestId + 1, ...partialElement } as Element + // Create full element + rows.shift() + const highestId = rows.reduce((id: number, row) => Math.max(id, +row.id || 0), 0) + const element = { id: highestId + 1, ...partialElement } as Element - // Add element - const stringifiedRow = stringifyElement(element, types) - await sheet.addRow(stringifiedRow) + // Add element + const stringifiedRow = stringifyElement(element, types) + await sheet.addRow(stringifiedRow) - return element + return element + }) } async function getGSheet(sheetName: string): Promise { diff --git a/src/gsheets/sequentialDBOperations.ts b/src/gsheets/sequentialDBOperations.ts new file mode 100644 index 0000000..62c3105 --- /dev/null +++ b/src/gsheets/sequentialDBOperations.ts @@ -0,0 +1,30 @@ +export default function sequentialDBOperations(): any { + interface Operation { + task: () => Promise + resolve: (value: OperationReturn) => void + reject: (reason: unknown) => void + } + + const operations: Operation[] = [] + + async function addDBOperation(task: () => Promise) { + return new Promise( + (resolve: (value: OperationReturn) => void, reject: (reason: unknown) => void) => { + operations.push({ task, resolve, reject }) + if (operations.length === 1) { + task().then(resolve).catch(reject).finally(runNextDBOperation) + } + } + ) + } + + function runNextDBOperation(): void { + operations.shift() + if (operations[0]) { + const { task, resolve, reject } = operations[0] + task().then(resolve).catch(reject).finally(runNextDBOperation) + } + } + + return addDBOperation +}