Fixes DB conflict in prod

This commit is contained in:
pikiou 2021-11-18 17:05:27 +01:00
parent 3d63859412
commit 581393f78e
12 changed files with 856 additions and 817 deletions

View File

@ -72,6 +72,7 @@
"@loadable/component": "^5.15.0", "@loadable/component": "^5.15.0",
"@loadable/server": "^5.15.0", "@loadable/server": "^5.15.0",
"@reduxjs/toolkit": "^1.6.0", "@reduxjs/toolkit": "^1.6.0",
"@types/lodash": "^4.14.177",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",
"axios": "^0.21.1", "axios": "^0.21.1",
"chalk": "^4.1.1", "chalk": "^4.1.1",
@ -92,6 +93,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"os": "^0.1.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
@ -127,7 +129,6 @@
"@types/loadable__component": "^5.13.4", "@types/loadable__component": "^5.13.4",
"@types/loadable__server": "^5.12.6", "@types/loadable__server": "^5.12.6",
"@types/loadable__webpack-plugin": "^5.7.3", "@types/loadable__webpack-plugin": "^5.7.3",
"@types/lodash": "^4.14.175",
"@types/mini-css-extract-plugin": "^2.0.1", "@types/mini-css-extract-plugin": "^2.0.1",
"@types/morgan": "^1.9.3", "@types/morgan": "^1.9.3",
"@types/react-dom": "^17.0.8", "@types/react-dom": "^17.0.8",

View File

@ -1,7 +1,7 @@
export default { export default {
HOST: "localhost", HOST: "localhost",
PORT: 3000, PORT: 3000,
API_URL: "", API_URL: "http://localhost:3000",
GOOGLE_SHEET_ID: "1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw", GOOGLE_SHEET_ID: "1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw",
APP: { APP: {
htmlAttributes: { lang: "en" }, htmlAttributes: { lang: "en" },

View File

@ -1,3 +1,9 @@
import os from "os"
const hostname = os.hostname()
export default { export default {
PORT: 4000, PORT: 4000,
HOST: hostname === "ns3075300" ? "fo.parisestludique.fr" : "localhost",
API_URL: hostname === "ns3075300" ? "http://fo.parisestludique.fr" : "http://localhost:4000",
} }

View File

@ -1,8 +1,13 @@
const CACHE_RENEW_DELAY = 10000 const CACHE_RENEW_DELAY = 10000
export default function DBManager<OperationReturn>(): any { const cache: { [sheetName: string]: any } = {}
const cacheTime: { [sheetName: string]: number } = {}
export default function DBManager<OperationReturn>(sheetName: string): any {
type OperationType = "add" | "list" | "set" type OperationType = "add" | "list" | "set"
cacheTime[sheetName] = 0
interface Operation { interface Operation {
task: () => Promise<OperationReturn> task: () => Promise<OperationReturn>
type: OperationType type: OperationType
@ -10,9 +15,6 @@ export default function DBManager<OperationReturn>(): any {
reject: (reason: unknown) => void reject: (reason: unknown) => void
} }
let cache: any
let cacheTime = 0
const operations: Operation[] = [] const operations: Operation[] = []
async function addDBOperation(type: OperationType, task: () => Promise<OperationReturn>) { async function addDBOperation(type: OperationType, task: () => Promise<OperationReturn>) {
@ -37,21 +39,21 @@ export default function DBManager<OperationReturn>(): any {
const { task, type, resolve, reject } = operation const { task, type, resolve, reject } = operation
if (type === "list") { if (type === "list") {
const now = +new Date() const now = +new Date()
if (now < cacheTime + CACHE_RENEW_DELAY) { if (now < cacheTime[sheetName] + CACHE_RENEW_DELAY) {
resolve(cache) resolve(cache[sheetName])
runNextDBOperation() runNextDBOperation()
} else { } else {
task() task()
.then((val: OperationReturn) => { .then((val: OperationReturn) => {
cache = val cache[sheetName] = val
cacheTime = now cacheTime[sheetName] = now
resolve(val) resolve(val)
}) })
.catch(reject) .catch(reject)
.finally(runNextDBOperation) .finally(runNextDBOperation)
} }
} else { } else {
cacheTime = 0 cacheTime[sheetName] = 0
task().then(resolve).catch(reject).finally(runNextDBOperation) task().then(resolve).catch(reject).finally(runNextDBOperation)
} }
} }

View File

@ -5,20 +5,21 @@ import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadshee
import DBManager from "./DBManager" import DBManager from "./DBManager"
const addDBOperation = DBManager()
const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json") const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
export type ElementWithId = unknown & { id: number } export type ElementWithId = unknown & { id: number }
export async function listGet<Element extends ElementWithId>( export default function getAccessors<
sheetName: string, // eslint-disable-next-line @typescript-eslint/ban-types
specimen: Element ElementNoId extends object,
): Promise<Element[]> { Element extends ElementNoId & ElementWithId
>(sheetName: string, specimen: Element): any {
const addDBOperation = DBManager(sheetName)
async function listGet(): Promise<Element[]> {
type StringifiedElement = Record<keyof Element, string> type StringifiedElement = Record<keyof Element, string>
return addDBOperation("list", async () => { return addDBOperation("list", async () => {
const sheet = await getGSheet(sheetName) const sheet = await getGSheet()
// Load sheet into an array of objects // Load sheet into an array of objects
const rows = (await sheet.getRows()) as StringifiedElement[] const rows = (await sheet.getRows()) as StringifiedElement[]
@ -33,7 +34,7 @@ export async function listGet<Element extends ElementWithId>(
keyof Element, keyof Element,
string string
> >
const element = parseElement<Element>(stringifiedElement, types, specimen) const element = parseElement(stringifiedElement, types)
if (element !== undefined) { if (element !== undefined) {
elements.push(element) elements.push(element)
} }
@ -41,24 +42,17 @@ export async function listGet<Element extends ElementWithId>(
return elements return elements
}) })
} }
export async function get<Element extends ElementWithId>( async function get(membreId: number): Promise<Element | undefined> {
sheetName: string,
membreId: number,
specimen: Element
): Promise<Element | undefined> {
// No need to addDBOperation here, since listGet does it already // No need to addDBOperation here, since listGet does it already
const list = await listGet<Element>(sheetName, specimen) const list = await listGet()
return list.find((element) => element.id === membreId) return list.find((element) => element.id === membreId)
} }
export async function setList<Element extends ElementWithId>( async function setList(elements: Element[]): Promise<true | undefined> {
sheetName: string,
elements: Element[]
): Promise<true | undefined> {
return addDBOperation("listSet", async () => { return addDBOperation("listSet", async () => {
const sheet = await getGSheet(sheetName) const sheet = await getGSheet()
// Load sheet into an array of objects // Load sheet into an array of objects
const rows = await sheet.getRows() const rows = await sheet.getRows()
@ -108,24 +102,24 @@ export async function setList<Element extends ElementWithId>(
return true return true
}) })
} }
export async function set<Element extends ElementWithId>( async function set(element: Element): Promise<Element | undefined> {
sheetName: string,
element: Element
): Promise<Element | undefined> {
if (!element) { if (!element) {
return undefined return undefined
} }
return addDBOperation("set", async () => { return addDBOperation("set", async () => {
const sheet = await getGSheet(sheetName) const sheet = await getGSheet()
// Load sheet into an array of objects // Load sheet into an array of objects
const rows = await sheet.getRows() const rows = await sheet.getRows()
if (!rows[0]) { if (!rows[0]) {
throw new Error(`No column types defined in sheet ${sheetName}`) throw new Error(`No column types defined in sheet ${sheetName}`)
} }
const types = _.pick(rows[0], Object.keys(element || {})) as Record<keyof Element, string> const types = _.pick(rows[0], Object.keys(element || {})) as Record<
keyof Element,
string
>
rows.shift() rows.shift()
// Replace previous row // Replace previous row
@ -138,18 +132,14 @@ export async function set<Element extends ElementWithId>(
await row.save() await row.save()
return element return element
}) })
} }
// eslint-disable-next-line @typescript-eslint/ban-types async function add(partialElement: Partial<ElementNoId>): Promise<Element | undefined> {
export async function add<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
sheetName: string,
partialElement: Partial<ElementNoId>
): Promise<Element | undefined> {
if (!partialElement) { if (!partialElement) {
return undefined return undefined
} }
return addDBOperation("add", async () => { return addDBOperation("add", async () => {
const sheet = await getGSheet(sheetName) const sheet = await getGSheet()
// Load sheet into an array of objects // Load sheet into an array of objects
const rows = await sheet.getRows() const rows = await sheet.getRows()
@ -175,23 +165,21 @@ export async function add<ElementNoId extends object, Element extends ElementNoI
return element return element
}) })
} }
async function getGSheet(sheetName: string): Promise<GoogleSpreadsheetWorksheet> { async function getGSheet(): Promise<GoogleSpreadsheetWorksheet> {
const doc = new GoogleSpreadsheet("1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw") const doc = new GoogleSpreadsheet("1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw")
const creds = await fs.readFile(CRED_PATH) const creds = await fs.readFile(CRED_PATH)
// Authentication // Authentication
await doc.useServiceAccountAuth(JSON.parse(creds.toString())) await doc.useServiceAccountAuth(JSON.parse(creds.toString()))
await doc.loadInfo() await doc.loadInfo()
return doc.sheetsByTitle[sheetName] return doc.sheetsByTitle[sheetName]
} }
// eslint-disable-next-line @typescript-eslint/ban-types function parseElement(
function parseElement<Element extends object>(
rawElement: Record<keyof Element, string>, rawElement: Record<keyof Element, string>,
types: Record<keyof Element, string>, types: Record<keyof Element, string>
specimen: Element ): Element {
): Element {
const fullElement = _.reduce( const fullElement = _.reduce(
types, types,
(element: any, type: string, prop: string) => { (element: any, type: string, prop: string) => {
@ -213,14 +201,20 @@ function parseElement<Element extends object>(
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const matchDate = rawProp.match(/^([0-9]+)\/([0-9]+)\/([0-9]+)$/) const matchDate = rawProp.match(/^([0-9]+)\/([0-9]+)\/([0-9]+)$/)
if (matchDate) { if (matchDate) {
element[prop] = new Date(+matchDate[3], +matchDate[2] - 1, +matchDate[1]) element[prop] = new Date(
+matchDate[3],
+matchDate[2] - 1,
+matchDate[1]
)
break break
} }
throw new Error(`Unable to read date from ${rawProp}`) throw new Error(`Unable to read date from ${rawProp}`)
default: default:
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/) const matchArrayType = type.match(
/^(number|string|boolean|date)\[([^\]]+)\]$/
)
if (!matchArrayType) { if (!matchArrayType) {
throw new Error(`Unknown array type for ${type}`) throw new Error(`Unknown array type for ${type}`)
} }
@ -268,7 +262,9 @@ function parseElement<Element extends object>(
return true return true
}) })
if (!rightFormat) { if (!rightFormat) {
throw new Error(`One array item is not a date in ${rawProp}`) throw new Error(
`One array item is not a date in ${rawProp}`
)
} }
break break
default: default:
@ -281,13 +277,12 @@ function parseElement<Element extends object>(
JSON.parse(JSON.stringify(specimen)) JSON.parse(JSON.stringify(specimen))
) )
return fullElement return fullElement
} }
// eslint-disable-next-line @typescript-eslint/ban-types function stringifyElement(
function stringifyElement<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
element: Element, element: Element,
types: { id: string } & Record<keyof ElementNoId, string> types: { id: string } & Record<keyof ElementNoId, string>
): Record<keyof Element, string> { ): Record<keyof Element, string> {
const rawElement: Record<keyof Element, string> = _.reduce( const rawElement: Record<keyof Element, string> = _.reduce(
types, types,
(stringifiedElement: Record<keyof Element, string>, type: string, prop: string) => { (stringifiedElement: Record<keyof Element, string>, type: string, prop: string) => {
@ -311,7 +306,9 @@ function stringifyElement<ElementNoId extends object, Element extends ElementNoI
default: default:
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/) const matchArrayType = type.match(
/^(number|string|boolean|date)\[([^\]]+)\]$/
)
if (!matchArrayType || !_.isArray(value)) { if (!matchArrayType || !_.isArray(value)) {
throw new Error( throw new Error(
"Unknown matchArrayType or not an array in stringifyElement" "Unknown matchArrayType or not an array in stringifyElement"
@ -355,7 +352,9 @@ function stringifyElement<ElementNoId extends object, Element extends ElementNoI
stringifiedElement[prop as keyof Element] = _.map( stringifiedElement[prop as keyof Element] = _.map(
value, value,
(val) => (val) =>
`${val.getDate()}/${val.getMonth() + 1}/${val.getFullYear()}` `${val.getDate()}/${
val.getMonth() + 1
}/${val.getFullYear()}`
).join(delimiter) ).join(delimiter)
break break
@ -370,13 +369,13 @@ function stringifyElement<ElementNoId extends object, Element extends ElementNoI
) )
return rawElement return rawElement
} }
function formulaSafe(value: string): string { function formulaSafe(value: string): string {
return value.replace(/^=+/, "") return value.replace(/^=+/, "")
} }
function stringifiedDate(value: unknown): string { function stringifiedDate(value: unknown): string {
let date: Date let date: Date
if (value instanceof Date) { if (value instanceof Date) {
date = value date = value
@ -390,5 +389,7 @@ function stringifiedDate(value: unknown): string {
throw new Error("Wrong date format in stringifyElement") throw new Error("Wrong date format in stringifyElement")
} }
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}` return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`
}
return { listGet, get, setList, set, add }
} }
export { SCOPES }

View File

@ -1,12 +1,15 @@
import { listGetRequest, getRequest, setRequest, addRequest } from "./expressAccessors" import getExpressAccessors from "./expressAccessors"
import { Envie, EnvieWithoutId } from "../services/envies" import { Envie, EnvieWithoutId } from "../services/envies"
const sheetName = "Envies d'aider" const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
EnvieWithoutId,
Envie
>("Envies d'aider", new Envie())
export const envieListGet = listGetRequest(sheetName, new Envie()) export const envieListGet = listGetRequest()
export const envieGet = getRequest(sheetName, new Envie()) export const envieGet = getRequest()
export const envieAdd = addRequest<EnvieWithoutId, Envie>(sheetName) export const envieAdd = addRequest()
export const envieSet = setRequest(sheetName) export const envieSet = setRequest()

View File

@ -1,11 +1,35 @@
import { Request, Response, NextFunction } from "express" import { Request, Response, NextFunction } from "express"
import { ElementWithId, get, listGet, add, set } from "./accessors" import getAccessors, { ElementWithId } from "./accessors"
export function getRequest<Element extends { id: number }>(sheetName: string, specimen: Element) { export default function getExpressAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types
ElementNoId extends object,
Element extends ElementNoId & ElementWithId
>(sheetName: string, specimen: Element): any {
const { get, listGet, add, set } = getAccessors(sheetName, specimen)
function listGetRequest() {
return async (
_request: Request,
response: Response,
_next: NextFunction
): Promise<void> => {
try {
const elements = await listGet()
if (elements) {
response.status(200).json(elements)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
}
function getRequest() {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => { return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try { try {
const id = parseInt(request.query.id as string, 10) || -1 const id = parseInt(request.query.id as string, 10) || -1
const elements = await get<Element>(sheetName, id, specimen) const elements = await get(id)
if (elements) { if (elements) {
response.status(200).json(elements) response.status(200).json(elements)
} }
@ -13,15 +37,12 @@ export function getRequest<Element extends { id: number }>(sheetName: string, sp
response.status(400).json(e) response.status(400).json(e)
} }
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types function addRequest() {
export function addRequest<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
sheetName: string
) {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => { return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try { try {
const element = await add<ElementNoId, Element>(sheetName, request.body) const element = await add(request.body)
if (element) { if (element) {
response.status(200).json(element) response.status(200).json(element)
} }
@ -29,28 +50,12 @@ export function addRequest<ElementNoId extends object, Element extends ElementNo
response.status(400).json(e) response.status(400).json(e)
} }
} }
} }
export function listGetRequest<Element extends { id: number }>( function setRequest() {
sheetName: string,
specimen: Element
) {
return async (_request: Request, response: Response, _next: NextFunction): Promise<void> => {
try {
const elements = await listGet<Element>(sheetName, specimen)
if (elements) {
response.status(200).json(elements)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
}
export function setRequest<Element extends { id: number }>(sheetName: string) {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => { return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try { try {
const element = await set<Element>(sheetName, request.body) const element = await set(request.body)
if (element) { if (element) {
response.status(200).json(element) response.status(200).json(element)
} }
@ -58,4 +63,7 @@ export function setRequest<Element extends { id: number }>(sheetName: string) {
response.status(400).json(e) response.status(400).json(e)
} }
} }
}
return { getRequest, addRequest, listGetRequest, setRequest }
} }

View File

@ -1,12 +1,15 @@
import { listGetRequest, getRequest, setRequest, addRequest } from "./expressAccessors" import getExpressAccessors from "./expressAccessors"
import { JeuJav, JeuJavWithoutId } from "../services/jeuxJav" import { JeuJav, JeuJavWithoutId } from "../services/jeuxJav"
const sheetName = "Jeux JAV" const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
JeuJavWithoutId,
JeuJav
>("Jeux JAV", new JeuJav())
export const jeuJavListGet = listGetRequest(sheetName, new JeuJav()) export const jeuJavListGet = listGetRequest()
export const jeuJavGet = getRequest(sheetName, new JeuJav()) export const jeuJavGet = getRequest()
export const jeuJavAdd = addRequest<JeuJavWithoutId, JeuJav>(sheetName) export const jeuJavAdd = addRequest()
export const jeuJavSet = setRequest(sheetName) export const jeuJavSet = setRequest()

View File

@ -1,12 +1,15 @@
import { listGetRequest, getRequest, setRequest, addRequest } from "./expressAccessors" import getExpressAccessors from "./expressAccessors"
import { Membre, MembreWithoutId } from "../services/membres" import { Membre, MembreWithoutId } from "../services/membres"
const sheetName = "Membres" const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
MembreWithoutId,
Membre
>("Membres", new Membre())
export const membreListGet = listGetRequest(sheetName, new Membre()) export const membreListGet = listGetRequest()
export const membreGet = getRequest(sheetName, new Membre()) export const membreGet = getRequest()
export const membreAdd = addRequest<MembreWithoutId, Membre>(sheetName) export const membreAdd = addRequest()
export const membreSet = setRequest(sheetName) export const membreSet = setRequest()

View File

@ -8,7 +8,6 @@ import LoadablePlugin from "@loadable/webpack-plugin"
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer" import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"
export const isDev = process.env.NODE_ENV === "development" export const isDev = process.env.NODE_ENV === "development"
const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => { const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => {
let loaders: RuleSetUseItem[] = [ let loaders: RuleSetUseItem[] = [
{ {

View File

@ -53,6 +53,10 @@ const config: Configuration = {
}, },
optimization: { minimizer: [new CssMinimizerPlugin()] }, optimization: { minimizer: [new CssMinimizerPlugin()] },
plugins: getPlugins(), plugins: getPlugins(),
performance: {
maxAssetSize: 512000,
maxEntrypointSize: 512000,
},
} }
export default merge(baseConfig(true), config) export default merge(baseConfig(true), config)

815
yarn.lock

File diff suppressed because it is too large Load Diff