mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-10 01:24:20 +02:00
Support in-memory read&write for db
This commit is contained in:
parent
fe06b2d45c
commit
7fb466d91c
@ -203,7 +203,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.formLine} key="line-already-volunteer">
|
<div className={styles.formLine} key="line-already-volunteer">
|
||||||
<div>
|
<div>
|
||||||
J'ai déjà été bénévole
|
J'ai déjà été bénévole à PeL
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="alreadyVolunteer"
|
name="alreadyVolunteer"
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
const CACHE_RENEW_DELAY = 10000
|
|
||||||
|
|
||||||
const cache: { [sheetName: string]: any } = {}
|
|
||||||
const cacheTime: { [sheetName: string]: number } = {}
|
|
||||||
|
|
||||||
export default function DBManager<OperationReturn>(sheetName: string): any {
|
|
||||||
type OperationType = "add" | "list" | "set"
|
|
||||||
|
|
||||||
cacheTime[sheetName] = 0
|
|
||||||
|
|
||||||
interface Operation {
|
|
||||||
task: () => Promise<OperationReturn>
|
|
||||||
type: OperationType
|
|
||||||
resolve: (value: OperationReturn) => void
|
|
||||||
reject: (reason: unknown) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const operations: Operation[] = []
|
|
||||||
|
|
||||||
async function addDBOperation(type: OperationType, task: () => Promise<OperationReturn>) {
|
|
||||||
return new Promise(
|
|
||||||
(resolve: (value: OperationReturn) => void, reject: (reason: unknown) => void) => {
|
|
||||||
operations.push({ task, type, resolve, reject })
|
|
||||||
if (operations.length === 1) {
|
|
||||||
runOperation(operations[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function runNextDBOperation(): void {
|
|
||||||
operations.shift()
|
|
||||||
if (operations[0]) {
|
|
||||||
runOperation(operations[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runOperation(operation: Operation): void {
|
|
||||||
const { task, type, resolve, reject } = operation
|
|
||||||
if (type === "list") {
|
|
||||||
const now = +new Date()
|
|
||||||
if (now < cacheTime[sheetName] + CACHE_RENEW_DELAY) {
|
|
||||||
resolve(cache[sheetName])
|
|
||||||
runNextDBOperation()
|
|
||||||
} else {
|
|
||||||
task()
|
|
||||||
.then((val: OperationReturn) => {
|
|
||||||
cache[sheetName] = val
|
|
||||||
cacheTime[sheetName] = now
|
|
||||||
resolve(val)
|
|
||||||
})
|
|
||||||
.catch(reject)
|
|
||||||
.finally(runNextDBOperation)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cacheTime[sheetName] = 0
|
|
||||||
task().then(resolve).catch(reject).finally(runNextDBOperation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return addDBOperation
|
|
||||||
}
|
|
@ -1,79 +1,180 @@
|
|||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadsheet"
|
import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadsheet"
|
||||||
|
|
||||||
import DBManager from "./DBManager"
|
// Test write attack with: wget --header='Content-Type:application/json' --post-data='{"prenom":"Pierre","nom":"SCELLES","email":"test@gmail.com","telephone":"0601010101","dejaBenevole":false,"commentaire":""}' http://localhost:3000/PreVolunteerAdd
|
||||||
|
|
||||||
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 }
|
const REMOTE_SAVE_DELAY = 20000
|
||||||
|
|
||||||
export const sheetNames: { [name: string]: string } = {
|
export type ElementWithId<ElementNoId> = { id: number } & ElementNoId
|
||||||
JavGames: "Jeux JAV",
|
|
||||||
Volunteers: "Membres",
|
export class SheetNames {
|
||||||
PreVolunteers: "PreMembres",
|
JavGames = "Jeux JAV"
|
||||||
Wishes: "Envies d'aider",
|
|
||||||
|
Volunteers = "Membres"
|
||||||
|
|
||||||
|
PreVolunteers = "PreMembres"
|
||||||
|
|
||||||
|
Wishes = "Envies d'aider"
|
||||||
}
|
}
|
||||||
|
export const sheetNames = new SheetNames()
|
||||||
|
|
||||||
export function getAccessors<
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
type SheetList = { [sheetName in keyof SheetNames]?: Sheet<object, ElementWithId<object>> }
|
||||||
|
const sheetList: SheetList = {}
|
||||||
|
setInterval(
|
||||||
|
() =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
Object.values(sheetList).forEach((sheet: Sheet<object, ElementWithId<object>>) =>
|
||||||
|
sheet.dbUpdate()
|
||||||
|
),
|
||||||
|
REMOTE_SAVE_DELAY
|
||||||
|
)
|
||||||
|
|
||||||
|
export function getSheet<
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
ElementNoId extends object,
|
ElementNoId extends object,
|
||||||
Element extends ElementNoId & ElementWithId
|
Element extends ElementNoId & ElementWithId<ElementNoId>
|
||||||
>(sheetName: string, specimen: Element, translation: { [k in keyof Element]: string }): any {
|
>(
|
||||||
const frenchSpecimen = _.mapValues(
|
sheetName: keyof SheetNames,
|
||||||
|
specimen: Element,
|
||||||
|
translation: { [k in keyof Element]: string }
|
||||||
|
): Sheet<ElementNoId, Element> {
|
||||||
|
if (!sheetList[sheetName]) {
|
||||||
|
sheetList[sheetName] = new Sheet<ElementNoId, Element>(sheetName, specimen, translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheetList[sheetName] as Sheet<ElementNoId, Element>
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sheet<
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
ElementNoId extends object,
|
||||||
|
Element extends ElementWithId<ElementNoId>
|
||||||
|
> {
|
||||||
|
sheetName: string
|
||||||
|
|
||||||
|
_state: Element[] | undefined
|
||||||
|
|
||||||
|
toRunAfterLoad: (() => void)[] | undefined = []
|
||||||
|
|
||||||
|
saveTimestamp = 0
|
||||||
|
|
||||||
|
modifiedSinceSave = false
|
||||||
|
|
||||||
|
frenchSpecimen: Element
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-useless-constructor
|
||||||
|
constructor(
|
||||||
|
readonly name: keyof SheetNames,
|
||||||
|
readonly specimen: Element,
|
||||||
|
readonly translation: { [k in keyof Element]: string }
|
||||||
|
) {
|
||||||
|
this.sheetName = sheetNames[name]
|
||||||
|
this.frenchSpecimen = _.mapValues(
|
||||||
_.invert(translation),
|
_.invert(translation),
|
||||||
(englishProp: string) => (specimen as any)[englishProp]
|
(englishProp: string) => (specimen as any)[englishProp]
|
||||||
) as Element
|
) as Element
|
||||||
|
|
||||||
const addDBOperation = DBManager(sheetName)
|
this.dbLoad()
|
||||||
|
|
||||||
async function listGet(): Promise<Element[]> {
|
|
||||||
type StringifiedElement = Record<keyof Element, string>
|
|
||||||
return addDBOperation("list", async () => {
|
|
||||||
const sheet = await getGSheet()
|
|
||||||
|
|
||||||
// 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.values(translation)) as Record<
|
|
||||||
keyof Element,
|
async getList(): Promise<Element[] | undefined> {
|
||||||
string
|
await this.waitForLoad()
|
||||||
>
|
return JSON.parse(JSON.stringify(this._state))
|
||||||
rows.shift()
|
}
|
||||||
rows.forEach((row) => {
|
|
||||||
const stringifiedElement = _.pick(row, Object.values(translation)) as Record<
|
setList(newState: Element[] | undefined) {
|
||||||
keyof Element,
|
this._state = JSON.parse(JSON.stringify(newState))
|
||||||
string
|
this.modifiedSinceSave = true
|
||||||
>
|
}
|
||||||
const element = parseElement(stringifiedElement, types)
|
|
||||||
if (element !== undefined) {
|
async nextId(): Promise<number> {
|
||||||
|
const list = await this.getList()
|
||||||
|
if (!list) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
const ids = _.map(list, "id")
|
||||||
|
return (_.max(ids) || 0) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(elementWithoutId: ElementNoId): Promise<Element> {
|
||||||
|
const elements: Element[] = (await this.getList()) || []
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
const element: Element = { id: await this.nextId(), ...elementWithoutId } as Element
|
||||||
elements.push(element)
|
elements.push(element)
|
||||||
|
await this.setList(elements)
|
||||||
|
return element
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return elements
|
async set(element: Element): Promise<void> {
|
||||||
|
const elements: Element[] = (await this.getList()) || []
|
||||||
|
const foundElement: Element | undefined = elements.find((e: Element) => e.id === element.id)
|
||||||
|
if (!foundElement) {
|
||||||
|
throw new Error(`No element found to be set in ${this.name} at id ${element.id}`)
|
||||||
|
}
|
||||||
|
if (!_.isEqual(foundElement, element)) {
|
||||||
|
Object.assign(foundElement, element)
|
||||||
|
await this.setList(elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runAfterLoad(func: () => void): void {
|
||||||
|
if (this.toRunAfterLoad) {
|
||||||
|
this.toRunAfterLoad.push(func)
|
||||||
|
} else {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForLoad(): Promise<void> {
|
||||||
|
return new Promise((resolve, _reject) => {
|
||||||
|
this.runAfterLoad(() => resolve(undefined))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(volunteerId: number): Promise<Element | undefined> {
|
dbUpdate(): void {
|
||||||
// No need to addDBOperation here, since listGet does it already
|
if (this.modifiedSinceSave) {
|
||||||
const list = await listGet()
|
this.dbSave()
|
||||||
return list.find((element) => element.id === volunteerId)
|
} else {
|
||||||
|
this.dbLoad()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setList(elements: Element[]): Promise<true | undefined> {
|
dbSave(): void {
|
||||||
return addDBOperation("listSet", async () => {
|
this.modifiedSinceSave = false
|
||||||
const sheet = await getGSheet()
|
this.saveTimestamp = +new Date()
|
||||||
|
|
||||||
|
this.dbSaveAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
dbLoad(): void {
|
||||||
|
this.toRunAfterLoad = []
|
||||||
|
this.dbLoadAsync().then(() => {
|
||||||
|
if (this.toRunAfterLoad) {
|
||||||
|
this.toRunAfterLoad.map((func) => func())
|
||||||
|
this.toRunAfterLoad = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async dbSaveAsync(): Promise<void> {
|
||||||
|
if (!this._state) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sheet = await this.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 ${this.name}`)
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
const elements = this._state as Element[]
|
||||||
const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record<
|
const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record<
|
||||||
keyof Element,
|
keyof Element,
|
||||||
string
|
string
|
||||||
@ -84,7 +185,7 @@ export function getAccessors<
|
|||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
const row = rows[rowid]
|
const row = rows[rowid]
|
||||||
const stringifiedRow = stringifyElement(element, types)
|
const stringifiedRow = this.stringifyElement(element, types)
|
||||||
|
|
||||||
if (!row) {
|
if (!row) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
@ -114,84 +215,48 @@ export function getAccessors<
|
|||||||
await rows[rowToDelete].delete()
|
await rows[rowToDelete].delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function set(element: Element): Promise<Element | undefined> {
|
private async dbLoadAsync(): Promise<void> {
|
||||||
if (!element) {
|
type StringifiedElement = Record<keyof Element, string>
|
||||||
return undefined
|
|
||||||
}
|
const sheet = await this.getGSheet()
|
||||||
return addDBOperation("set", async () => {
|
|
||||||
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()) as StringifiedElement[]
|
||||||
|
const elements: Element[] = []
|
||||||
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 ${this.name}`)
|
||||||
}
|
}
|
||||||
const types = _.pick(rows[0], Object.keys(element || {})) as Record<
|
const types = _.pick(rows[0], Object.values(this.translation)) as Record<
|
||||||
keyof Element,
|
keyof Element,
|
||||||
string
|
string
|
||||||
>
|
>
|
||||||
rows.shift()
|
rows.shift()
|
||||||
|
rows.forEach((row) => {
|
||||||
// Replace previous row
|
const stringifiedElement = _.pick(row, Object.values(this.translation)) as Record<
|
||||||
const stringifiedRow = stringifyElement(element, types)
|
keyof Element,
|
||||||
const row = rows.find((rowItem) => +rowItem.id === element.id)
|
|
||||||
if (!row) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
Object.assign(row, stringifiedRow)
|
|
||||||
await row.save()
|
|
||||||
return element
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function add(partialElement: Partial<ElementNoId>): Promise<Element | undefined> {
|
|
||||||
if (!partialElement) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return addDBOperation("add", async () => {
|
|
||||||
const sheet = await getGSheet()
|
|
||||||
|
|
||||||
// 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
|
string
|
||||||
>),
|
>
|
||||||
|
const element = this.parseElement(stringifiedElement, types)
|
||||||
|
if (element !== undefined) {
|
||||||
|
elements.push(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)
|
|
||||||
|
|
||||||
return element
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this._state = elements
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getGSheet(): Promise<GoogleSpreadsheetWorksheet> {
|
private async 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[this.sheetName]
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseElement(
|
private parseElement(
|
||||||
rawElement: Record<keyof Element, string>,
|
rawElement: Record<keyof Element, string>,
|
||||||
types: Record<keyof Element, string>
|
types: Record<keyof Element, string>
|
||||||
): Element {
|
): Element {
|
||||||
@ -201,29 +266,47 @@ export function getAccessors<
|
|||||||
const rawProp: string = rawElement[prop as keyof Element]
|
const rawProp: string = rawElement[prop as keyof Element]
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "string":
|
case "string":
|
||||||
|
if (rawProp === undefined) {
|
||||||
|
element[prop] = ""
|
||||||
|
} else {
|
||||||
element[prop] = rawProp
|
element[prop] = rawProp
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case "number":
|
case "number":
|
||||||
|
if (rawProp === undefined) {
|
||||||
|
element[prop] = undefined
|
||||||
|
} else {
|
||||||
element[prop] = +rawProp
|
element[prop] = +rawProp
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case "boolean":
|
case "boolean":
|
||||||
|
if (rawProp === undefined) {
|
||||||
|
element[prop] = false
|
||||||
|
} else {
|
||||||
element[prop] = rawProp !== "0" && rawProp !== ""
|
element[prop] = rawProp !== "0" && rawProp !== ""
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case "date":
|
case "date":
|
||||||
|
if (rawProp === undefined) {
|
||||||
|
element[prop] = undefined
|
||||||
|
} else {
|
||||||
// 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) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to read date from val ${rawProp} in sheet ${this.name} at prop ${prop}`
|
||||||
|
)
|
||||||
|
}
|
||||||
element[prop] = new Date(
|
element[prop] = new Date(
|
||||||
+matchDate[3],
|
+matchDate[3],
|
||||||
+matchDate[2] - 1,
|
+matchDate[2] - 1,
|
||||||
+matchDate[1]
|
+matchDate[1]
|
||||||
)
|
)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
throw new Error(`Unable to read date from ${rawProp}`)
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line no-case-declarations
|
// eslint-disable-next-line no-case-declarations
|
||||||
@ -231,9 +314,11 @@ export function getAccessors<
|
|||||||
/^(number|string|boolean|date)\[([^\]]+)\]$/
|
/^(number|string|boolean|date)\[([^\]]+)\]$/
|
||||||
)
|
)
|
||||||
if (!matchArrayType) {
|
if (!matchArrayType) {
|
||||||
throw new Error(`Unknown array type for ${type}`)
|
throw new Error(
|
||||||
|
`Unknown array type for ${type} in sheet ${this.name} at prop ${prop}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!rawProp) {
|
if (rawProp === undefined || rawProp === "") {
|
||||||
element[prop] = []
|
element[prop] = []
|
||||||
} else {
|
} else {
|
||||||
const arrayType = matchArrayType[1]
|
const arrayType = matchArrayType[1]
|
||||||
@ -278,25 +363,27 @@ export function getAccessors<
|
|||||||
})
|
})
|
||||||
if (!rightFormat) {
|
if (!rightFormat) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`One array item is not a date in ${rawProp}`
|
`One array item is not a date for val ${rawProp} in sheet ${this.name} at prop ${prop}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown array type ${arrayType}`)
|
throw new Error(
|
||||||
|
`Unknown array type ${arrayType} in sheet ${this.name} at prop ${prop}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return element
|
return element
|
||||||
},
|
},
|
||||||
JSON.parse(JSON.stringify(frenchSpecimen))
|
JSON.parse(JSON.stringify(this.frenchSpecimen))
|
||||||
)
|
)
|
||||||
return fullElement
|
return fullElement
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyElement(
|
private stringifyElement(
|
||||||
element: Element,
|
element: Element,
|
||||||
types: { id: string } & Record<keyof ElementNoId, string>
|
types: 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,
|
types,
|
||||||
@ -304,7 +391,7 @@ export function getAccessors<
|
|||||||
const value = element[prop as keyof Element]
|
const value = element[prop as keyof Element]
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "string":
|
case "string":
|
||||||
stringifiedElement[prop as keyof Element] = formulaSafe(`${value}`)
|
stringifiedElement[prop as keyof Element] = Sheet.formulaSafe(`${value}`)
|
||||||
break
|
break
|
||||||
|
|
||||||
case "number":
|
case "number":
|
||||||
@ -316,7 +403,7 @@ export function getAccessors<
|
|||||||
break
|
break
|
||||||
|
|
||||||
case "date":
|
case "date":
|
||||||
stringifiedElement[prop as keyof Element] = stringifiedDate(value)
|
stringifiedElement[prop as keyof Element] = Sheet.stringifiedDate(value)
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -339,7 +426,7 @@ export function getAccessors<
|
|||||||
if (!_.every(value, _.isString)) {
|
if (!_.every(value, _.isString)) {
|
||||||
throw new Error(`Each date of ${value} is not a string`)
|
throw new Error(`Each date of ${value} is not a string`)
|
||||||
}
|
}
|
||||||
stringifiedElement[prop as keyof Element] = formulaSafe(
|
stringifiedElement[prop as keyof Element] = Sheet.formulaSafe(
|
||||||
value.join(delimiter)
|
value.join(delimiter)
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
@ -380,17 +467,17 @@ export function getAccessors<
|
|||||||
|
|
||||||
return stringifiedElement
|
return stringifiedElement
|
||||||
},
|
},
|
||||||
JSON.parse(JSON.stringify(frenchSpecimen))
|
JSON.parse(JSON.stringify(this.frenchSpecimen))
|
||||||
)
|
)
|
||||||
|
|
||||||
return rawElement
|
return rawElement
|
||||||
}
|
}
|
||||||
|
|
||||||
function formulaSafe(value: string): string {
|
private static formulaSafe(value: string): string {
|
||||||
return value.replace(/^=+/, "")
|
return value.replace(/^=+/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifiedDate(value: unknown): string {
|
private static stringifiedDate(value: unknown): string {
|
||||||
let date: Date
|
let date: Date
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
date = value
|
date = value
|
||||||
@ -405,6 +492,4 @@ export function getAccessors<
|
|||||||
}
|
}
|
||||||
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`
|
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return { listGet, get, setList, set, add }
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { Request, Response, NextFunction } from "express"
|
import { Request, Response, NextFunction } from "express"
|
||||||
import { ElementWithId, getAccessors } from "./accessors"
|
import { SheetNames, ElementWithId, getSheet } from "./accessors"
|
||||||
|
|
||||||
export default function getExpressAccessors<
|
export default function getExpressAccessors<
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
ElementNoId extends object,
|
ElementNoId extends object,
|
||||||
Element extends ElementNoId & ElementWithId
|
Element extends ElementWithId<ElementNoId>
|
||||||
>(sheetName: string, specimen: Element, translation: { [k in keyof Element]: string }): any {
|
>(
|
||||||
const { get, listGet, add, set } = getAccessors(sheetName, specimen, translation)
|
sheetName: keyof SheetNames,
|
||||||
|
specimen: Element,
|
||||||
|
translation: { [k in keyof Element]: string }
|
||||||
|
): any {
|
||||||
|
const sheet = getSheet<ElementNoId, Element>(sheetName, specimen, translation)
|
||||||
|
|
||||||
function listGetRequest() {
|
function listGetRequest() {
|
||||||
return async (
|
return async (
|
||||||
@ -15,7 +19,7 @@ export default function getExpressAccessors<
|
|||||||
_next: NextFunction
|
_next: NextFunction
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const elements = await listGet()
|
const elements = await sheet.getList()
|
||||||
if (elements) {
|
if (elements) {
|
||||||
response.status(200).json(elements)
|
response.status(200).json(elements)
|
||||||
}
|
}
|
||||||
@ -29,9 +33,10 @@ export default function getExpressAccessors<
|
|||||||
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(id)
|
const elements = await sheet.getList()
|
||||||
if (elements) {
|
if (elements) {
|
||||||
response.status(200).json(elements)
|
const element = elements.find((e: Element) => e.id === id)
|
||||||
|
response.status(200).json(element)
|
||||||
}
|
}
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
response.status(400).json(e)
|
response.status(400).json(e)
|
||||||
@ -42,7 +47,11 @@ export default function getExpressAccessors<
|
|||||||
function addRequest() {
|
function addRequest() {
|
||||||
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(request.body)
|
sheet.add(request.body)
|
||||||
|
const elements: Element[] = (await sheet.getList()) || []
|
||||||
|
const element: Element = { id: await sheet.nextId(), ...request.body }
|
||||||
|
elements.push(element)
|
||||||
|
await sheet.setList(elements)
|
||||||
if (element) {
|
if (element) {
|
||||||
response.status(200).json(element)
|
response.status(200).json(element)
|
||||||
}
|
}
|
||||||
@ -55,10 +64,8 @@ export default function getExpressAccessors<
|
|||||||
function setRequest() {
|
function setRequest() {
|
||||||
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(request.body)
|
await sheet.set(request.body)
|
||||||
if (element) {
|
response.status(200)
|
||||||
response.status(200).json(element)
|
|
||||||
}
|
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
response.status(400).json(e)
|
response.status(400).json(e)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import getExpressAccessors from "./expressAccessors"
|
import getExpressAccessors from "./expressAccessors"
|
||||||
import { sheetNames } from "./accessors"
|
|
||||||
import { JavGame, JavGameWithoutId, translationJavGame } from "../../services/javGames"
|
import { JavGame, JavGameWithoutId, translationJavGame } from "../../services/javGames"
|
||||||
|
|
||||||
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
||||||
JavGameWithoutId,
|
JavGameWithoutId,
|
||||||
JavGame
|
JavGame
|
||||||
>(sheetNames.JavGames, new JavGame(), translationJavGame)
|
>("JavGames", new JavGame(), translationJavGame)
|
||||||
|
|
||||||
export const javGameListGet = listGetRequest()
|
export const javGameListGet = listGetRequest()
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import getExpressAccessors from "./expressAccessors"
|
import getExpressAccessors from "./expressAccessors"
|
||||||
import { sheetNames } from "./accessors"
|
|
||||||
import {
|
import {
|
||||||
PreVolunteer,
|
PreVolunteer,
|
||||||
PreVolunteerWithoutId,
|
PreVolunteerWithoutId,
|
||||||
@ -9,7 +8,7 @@ import {
|
|||||||
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
||||||
PreVolunteerWithoutId,
|
PreVolunteerWithoutId,
|
||||||
PreVolunteer
|
PreVolunteer
|
||||||
>(sheetNames.PreVolunteers, new PreVolunteer(), translationPreVolunteer)
|
>("PreVolunteers", new PreVolunteer(), translationPreVolunteer)
|
||||||
|
|
||||||
export const preVolunteerListGet = listGetRequest()
|
export const preVolunteerListGet = listGetRequest()
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import getExpressAccessors from "./expressAccessors"
|
import getExpressAccessors from "./expressAccessors"
|
||||||
import { sheetNames } from "./accessors"
|
|
||||||
import { Volunteer, VolunteerWithoutId, translationVolunteer } from "../../services/volunteers"
|
import { Volunteer, VolunteerWithoutId, translationVolunteer } from "../../services/volunteers"
|
||||||
|
|
||||||
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
||||||
VolunteerWithoutId,
|
VolunteerWithoutId,
|
||||||
Volunteer
|
Volunteer
|
||||||
>(sheetNames.Volunteers, new Volunteer(), translationVolunteer)
|
>("Volunteers", new Volunteer(), translationVolunteer)
|
||||||
|
|
||||||
export const volunteerListGet = listGetRequest()
|
export const volunteerListGet = listGetRequest()
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import getExpressAccessors from "./expressAccessors"
|
import getExpressAccessors from "./expressAccessors"
|
||||||
import { sheetNames } from "./accessors"
|
|
||||||
import { Wish, WishWithoutId, translationWish } from "../../services/wishes"
|
import { Wish, WishWithoutId, translationWish } from "../../services/wishes"
|
||||||
|
|
||||||
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
|
||||||
WishWithoutId,
|
WishWithoutId,
|
||||||
Wish
|
Wish
|
||||||
>(sheetNames.Wishes, new Wish(), translationWish)
|
>("Wishes", new Wish(), translationWish)
|
||||||
|
|
||||||
export const wishListGet = listGetRequest()
|
export const wishListGet = listGetRequest()
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { getAccessors } from "../../gsheets/accessors"
|
import { getSheet } from "../../gsheets/accessors"
|
||||||
import { login } from "../login"
|
import { login } from "../login"
|
||||||
|
|
||||||
// Could do a full test with: wget --header='Content-Type:application/json' --post-data='{"email":"pikiou.sub@gmail.com","password":"mot de passe"}' http://localhost:3000/api/user/login
|
// Could do a full test with: wget --header='Content-Type:application/json' --post-data='{"email":"pikiou.sub@gmail.com","password":"mot de passe"}' http://localhost:3000/api/user/login
|
||||||
@ -20,8 +20,8 @@ jest.mock("../../gsheets/accessors")
|
|||||||
|
|
||||||
describe("login with", () => {
|
describe("login with", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
;(getAccessors as jest.Mock).mockImplementation(() => ({
|
;(getSheet as jest.Mock).mockImplementation(() => ({
|
||||||
listGet: () => [mockUser],
|
getList: async () => [mockUser],
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,12 +2,13 @@ import { Request, Response, NextFunction } from "express"
|
|||||||
import bcrypt from "bcrypt"
|
import bcrypt from "bcrypt"
|
||||||
import {
|
import {
|
||||||
Volunteer,
|
Volunteer,
|
||||||
|
VolunteerWithoutId,
|
||||||
VolunteerLogin,
|
VolunteerLogin,
|
||||||
emailRegexp,
|
emailRegexp,
|
||||||
passwordMinLength,
|
passwordMinLength,
|
||||||
translationVolunteer,
|
translationVolunteer,
|
||||||
} from "../../services/volunteers"
|
} from "../../services/volunteers"
|
||||||
import { getAccessors, sheetNames } from "../gsheets/accessors"
|
import { getSheet } from "../gsheets/accessors"
|
||||||
import { getJwt } from "../secure"
|
import { getJwt } from "../secure"
|
||||||
|
|
||||||
export default async function loginHandler(
|
export default async function loginHandler(
|
||||||
@ -31,7 +32,11 @@ export default async function loginHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function login(rawEmail: string, rawPassword: string): Promise<VolunteerLogin> {
|
export async function login(rawEmail: string, rawPassword: string): Promise<VolunteerLogin> {
|
||||||
const { listGet } = getAccessors(sheetNames.Volunteers, new Volunteer(), translationVolunteer)
|
const sheet = getSheet<VolunteerWithoutId, Volunteer>(
|
||||||
|
"Volunteers",
|
||||||
|
new Volunteer(),
|
||||||
|
translationVolunteer
|
||||||
|
)
|
||||||
|
|
||||||
const email = rawEmail.replace(/^\s*/, "").replace(/\s*$/, "")
|
const email = rawEmail.replace(/^\s*/, "").replace(/\s*$/, "")
|
||||||
if (!emailRegexp.test(email)) {
|
if (!emailRegexp.test(email)) {
|
||||||
@ -46,8 +51,8 @@ export async function login(rawEmail: string, rawPassword: string): Promise<Volu
|
|||||||
throw Error("Mot de passe trop court")
|
throw Error("Mot de passe trop court")
|
||||||
}
|
}
|
||||||
|
|
||||||
const volunteers: Volunteer[] = await listGet()
|
const volunteers: Volunteer[] | undefined = await sheet.getList()
|
||||||
const volunteer = volunteers.find((m) => m.email === email)
|
const volunteer = volunteers && volunteers.find((m) => m.email === email)
|
||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error("Cet email ne correspond à aucun utilisateur")
|
throw Error("Cet email ne correspond à aucun utilisateur")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user