This commit is contained in:
ChatonDeAru 2024-03-23 18:53:24 +01:00
parent 74685fa741
commit 42c7410bfe
No known key found for this signature in database
GPG Key ID: 3CADF765A409F755
29 changed files with 176 additions and 740 deletions

View File

@ -8,7 +8,3 @@ GSHEET_ID=
DISCORD_TOKEN= DISCORD_TOKEN=
DISCORD_CLIENTID= DISCORD_CLIENTID=
DISCORD_GUILDID= DISCORD_GUILDID=
## Notifications
FORCE_ORANGE_PUBLIC_VAPID_KEY=
FORCE_ORANGE_PRIVATE_VAPID_KEY=

View File

@ -55,11 +55,10 @@ module.exports = {
"jsx-a11y/control-has-associated-label": "off", "jsx-a11y/control-has-associated-label": "off",
}, },
globals: { globals: {
__CLIENT__: true, API_URL: false,
__SERVER__: true, SSR: true,
__DEV__: true, DEV: true,
__LOCAL__: false, REGISTER_DISCORD_COMMANDS: false,
__REGISTER_DISCORD_COMMANDS__: false, TEST: false,
__TEST__: false,
}, },
} }

View File

@ -31,6 +31,6 @@ COPY . .
## Build the app ## Build the app
RUN yarn run build RUN yarn run build
EXPOSE 8080 EXPOSE 4000
CMD ["yarn", "start"] CMD ["yarn", "run", "start"]

View File

@ -54,7 +54,7 @@ Now the app should be serving at <http://localhost:3000>.
## Using Docker ## Using Docker
`docker build . -t force-orange` `docker build . -t force-orange`
`docker run -d -p 3000:8080 -t force-orange` `docker run -d -p 3000:3000 -t force-orange`
### Commands ### Commands

View File

@ -18,12 +18,11 @@ module.exports = {
"<rootDir>/jest/assetMock.ts", "<rootDir>/jest/assetMock.ts",
}, },
globals: { globals: {
__DEV__: true, API_URL: "http://localhost:3000",
__CLIENT__: true, DEV: true,
__SERVER__: false, SSR: false,
__LOCAL__: false, REGISTER_DISCORD_COMMANDS: false,
__REGISTER_DISCORD_COMMANDS__: false, TEST: true,
__TEST__: true,
}, },
maxConcurrency: 50, maxConcurrency: 50,
maxWorkers: 1, maxWorkers: 1,

View File

@ -43,9 +43,9 @@
"dev": "yarn dev:build && nodemon -r dotenv/config ./public/server", "dev": "yarn dev:build && nodemon -r dotenv/config ./public/server",
"ser": "yarn dev:build && node -r dotenv/config ./public/server", "ser": "yarn dev:build && node -r dotenv/config ./public/server",
"dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts", "dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts",
"local-start": "cross-env LOCAL=true yarn build && node ./public/server", "local-start": "cross-env LOCAL=true yarn build && node -r dotenv/config ./public/server",
"discord-register": "cross-env REGISTER_DISCORD_COMMANDS=true yarn build && node ./public/server", "discord-register": "cross-env REGISTER_DISCORD_COMMANDS=true yarn build && node -r dotenv/config ./public/server",
"start": "node ./public/server", "start": "node -r dotenv/config ./public/server",
"build": "run-s build:*", "build": "run-s build:*",
"build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts", "build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts",
"build:client": "cross-env NODE_ENV=production webpack --config ./webpack/client.config.ts", "build:client": "cross-env NODE_ENV=production webpack --config ./webpack/client.config.ts",
@ -82,8 +82,6 @@
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",
"@types/react-modal": "^3.13.1", "@types/react-modal": "^3.13.1",
"@types/serviceworker": "^0.0.32",
"@types/web-push": "^3.3.2",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",
"axios": "^0.21.1", "axios": "^0.21.1",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
@ -127,7 +125,6 @@
"redux-thunk": "^2.4.2", "redux-thunk": "^2.4.2",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",
"serve-favicon": "^2.5.0", "serve-favicon": "^2.5.0",
"web-push": "^3.4.5",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },
"devDependencies": { "devDependencies": {

45
scripts/deploy.sh Normal file
View File

@ -0,0 +1,45 @@
#!/bin/bash
# Script from :
# https://emilwypych.com/2020/10/25/how-to-run-discord-bot-on-cloud-run/?cn-reloaded=1
GCP_CONFIGURATION_NAME=barajeuxonline
GCP_PROJECT_NAME=barajeuxonline
GCP_REGION=europe-west1
PROJECT_NAME=barajeuxonline-bot
if [ -z "$1" ]
then
ENVSUFFIX="test"
else
ENVSUFFIX=$1
fi
# Ensure proper GCP configuration is set
echo "[GCloud] get configuration"
gcloud config configurations activate ${GCP_CONFIGURATION_NAME}
# rm data/auth.json
# cp data/auth-"${ENVSUFFIX}".json data/auth.json
echo "[Docker] Build a new docker image"
docker build --no-cache -t gcr.io/${GCP_PROJECT_NAME}/"${PROJECT_NAME}-${ENVSUFFIX}" .
echo "[Docker] Push the new docker image"
docker push gcr.io/${GCP_PROJECT_NAME}/"${PROJECT_NAME}-${ENVSUFFIX}"
echo "[GCloud] Deploy new revision of ${PROJECT_NAME}-${ENVSUFFIX}"
gcloud run deploy "${PROJECT_NAME}-${ENVSUFFIX}" --image=gcr.io/${GCP_PROJECT_NAME}/"${PROJECT_NAME}-${ENVSUFFIX}" \
--platform=managed --region=${GCP_REGION} --allow-unauthenticated \
--max-instances 1 --memory=2Gi --cpu=2
echo "[GCloud] Ensure that there is cron job for checking ${PROJECT_NAME}-${ENVSUFFIX}"
# Get proper URL
GCP_APP_URL=$(gcloud run services list --platform=managed --region=${GCP_REGION} \
--filter="status.address.url ~ ${PROJECT_NAME}-${ENVSUFFIX}" \
--format="value(status.address.url)")
gcloud scheduler jobs create http GET-"${PROJECT_NAME}-${ENVSUFFIX}" \
--schedule="* * * * *" --uri="${GCP_APP_URL}" --http-method GET

View File

@ -2,7 +2,6 @@ import { RouteConfig, renderRoutes } from "react-router-config"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import { ToastContainer } from "react-toastify" import { ToastContainer } from "react-toastify"
import config from "../config"
// Import your global styles here // Import your global styles here
import "normalize.css/normalize.css" import "normalize.css/normalize.css"
import "react-toastify/dist/ReactToastify.css" import "react-toastify/dist/ReactToastify.css"
@ -17,6 +16,19 @@ interface Route {
export const reactAppId = "react-view" export const reactAppId = "react-view"
const app = {
htmlAttributes: { lang: "en" },
title: "Force Orange",
description: "Le site des bénévoles",
titleTemplate: "Force Orange - %s",
meta: [
{
name: "description",
content: "The best react universal starter boilerplate in the world.",
},
],
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const App = ({ route, location }: Route): JSX.Element => { const App = ({ route, location }: Route): JSX.Element => {
if (location.pathname === "/fiches") { if (location.pathname === "/fiches") {
@ -26,7 +38,7 @@ const App = ({ route, location }: Route): JSX.Element => {
return ( return (
<div> <div>
<Helmet {...config.APP}> <Helmet {...app}>
<meta <meta
name="viewport" name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
@ -36,9 +48,9 @@ const App = ({ route, location }: Route): JSX.Element => {
<div className={styles.logo} /> <div className={styles.logo} />
<div> <div>
<h1 className={styles.siteName}> <h1 className={styles.siteName}>
<a href="/">{config.APP.title}</a> <a href="/">Force Orange</a>
</h1> </h1>
<div className={styles.siteDescription}>{config.APP.description}</div> <div className={styles.siteDescription}>Le site des bénévoles</div>
</div> </div>
<div className={styles.menuWrapper}> <div className={styles.menuWrapper}>
<MainMenu /> <MainMenu />

View File

@ -1,229 +0,0 @@
import _ from "lodash"
import React, { useCallback, useEffect, useRef, useState } from "react"
import isNode from "detect-node"
import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
import styles from "./styles.module.scss"
import { useAskTools, addAsk } from "./utils"
import FormButton from "../Form/FormButton/FormButton"
import { toastError, toastSuccess } from "../../store/utils"
export function AskPushNotif(asks: JSX.Element[], id: number): void {
const { dispatch, jwtToken, volunteerAsks } = useAskTools()
const [acceptsNotifs, setAcceptsNotifs] = useState("")
const mounted = useRef(false)
useEffect(() => {
if (mounted.current) {
return
}
mounted.current = true
if (!isNode) {
if (volunteerAsks?.acceptsNotifs === "oui") {
navigator.serviceWorker.ready.then((registration) =>
registration.pushManager.getSubscription().then((existedSubscription) => {
const doesAcceptNotifs =
_.isEqual(
JSON.parse(JSON.stringify(existedSubscription)),
JSON.parse(volunteerAsks?.pushNotifSubscription)
) && volunteerAsks?.acceptsNotifs === "oui"
setAcceptsNotifs(doesAcceptNotifs ? "oui" : "non")
})
)
} else {
setAcceptsNotifs("non")
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const onChangeValue = (e: React.ChangeEvent<HTMLInputElement>) =>
setAcceptsNotifs(e.target.value)
const onSubmit = useCallback(async (): Promise<void> => {
if (isNode) {
return
}
if (!("serviceWorker" in navigator)) {
return
}
if (acceptsNotifs === "non") {
dispatch(
fetchVolunteerAsksSet(jwtToken, 0, {
hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
acceptsNotifs: "non",
})
)
return
}
const registration = await navigator.serviceWorker.ready
if (!registration.pushManager) {
toastError(
"Il y a un problème avec le push manager. Il faudrait utiliser un navigateur plus récent !"
)
dispatch(
fetchVolunteerAsksSet(jwtToken, 0, {
hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
})
)
return
}
const convertedVapidKey = urlBase64ToUint8Array(process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY)
function urlBase64ToUint8Array(base64String?: string) {
if (!base64String) {
return ""
}
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
// eslint-disable-next-line
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/")
const rawData = atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; i += 1) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
if (!convertedVapidKey) {
toastError("No convertedVapidKey available")
}
try {
const existedSubscription = await registration.pushManager.getSubscription()
if (existedSubscription === null) {
// No subscription detected, make a request
try {
const newSubscription = await registration.pushManager.subscribe({
applicationServerKey: convertedVapidKey,
userVisibleOnly: true,
})
// New subscription added
if (
volunteerAsks?.acceptsNotifs === "oui" &&
!subscriptionEqualsSave(
newSubscription,
volunteerAsks?.pushNotifSubscription
)
) {
toastSuccess(
"Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
)
}
dispatch(
fetchVolunteerAsksSet(jwtToken, 0, {
pushNotifSubscription: JSON.stringify(newSubscription),
acceptsNotifs: "oui",
hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
})
)
} catch (_e) {
if (Notification.permission !== "granted") {
toastError(
"Mince tu as bloqué les notifications pour le site des bénévoles ! En haut juste à gauche de la barre d'adresse, il y a une icone de cadenas ou de message barré sur lequel cliquer pour annuler ce blocage.",
false
)
} else {
toastError(
"Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !"
)
}
}
} else {
// Existed subscription detected
if (
volunteerAsks?.acceptsNotifs === "oui" &&
!subscriptionEqualsSave(
existedSubscription,
volunteerAsks?.pushNotifSubscription
)
) {
toastSuccess(
"Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
)
}
dispatch(
fetchVolunteerAsksSet(jwtToken, 0, {
pushNotifSubscription: JSON.stringify(existedSubscription),
acceptsNotifs: "oui",
hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
})
)
}
} catch (_e) {
toastError(
"Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !"
)
}
}, [
acceptsNotifs,
dispatch,
id,
jwtToken,
volunteerAsks?.acceptsNotifs,
volunteerAsks?.hiddenAsks,
volunteerAsks?.pushNotifSubscription,
])
function subscriptionEqualsSave(toCheck: PushSubscription, save: string | undefined): boolean {
if (!save) {
return !toCheck
}
return _.isEqual(JSON.parse(JSON.stringify(toCheck)), JSON.parse(save))
}
const needToShow =
volunteerAsks?.acceptsNotifs !== "oui" && volunteerAsks?.acceptsNotifs !== "non"
addAsk(
asks,
id,
volunteerAsks,
true,
needToShow,
<div className={styles.formLine}>
<label>
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en aura
d&apos;autres à t'afficher ici ?<br />
<span className={styles.sousMessage}>
(Ça nous simplifierait la vie, on a des soucis à contacter les bénévoles par
email.)
</span>
<label>
<input
type="radio"
value="oui"
name="notifs"
checked={acceptsNotifs === "oui"}
onChange={onChangeValue}
/>{" "}
Oui
</label>
<label>
<input
type="radio"
value="non"
name="notifs"
checked={acceptsNotifs === "non"}
onChange={onChangeValue}
/>{" "}
Non
</label>
</label>
<div className={styles.formButtons}>
<FormButton onClick={onSubmit}>Enregistrer</FormButton>
</div>
</div>
)
}

View File

@ -17,7 +17,6 @@ import {
} from "./AskParticipationDetails" } from "./AskParticipationDetails"
import { OnSiteInfo, fetchFor as fetchForOnSiteInfo } from "./OnSiteInfo" import { OnSiteInfo, fetchFor as fetchForOnSiteInfo } from "./OnSiteInfo"
// import { AskPushNotif } from "./AskPushNotif"
const Asks = (): JSX.Element | null => { const Asks = (): JSX.Element | null => {
const { volunteerAsks } = useAskTools() const { volunteerAsks } = useAskTools()
@ -35,8 +34,6 @@ const Asks = (): JSX.Element | null => {
AskHosting(asks, 20) AskHosting(asks, 20)
AskParticipationDetails(asks, 22) AskParticipationDetails(asks, 22)
// AskPushNotif(asks, 99)
const onSiteInfoElement = OnSiteInfo() const onSiteInfoElement = OnSiteInfo()
if (_.isEmpty(asks)) { if (_.isEmpty(asks)) {
asks.push(onSiteInfoElement) asks.push(onSiteInfoElement)

View File

@ -13,14 +13,6 @@
@include page-content-wrapper(520px); @include page-content-wrapper(520px);
} }
.pushNotificationsPage {
@include page-wrapper-center;
}
.pushNotificationsContent {
@include page-content-wrapper;
}
.title { .title {
padding-bottom: 10px; padding-bottom: 10px;
font-weight: bold; font-weight: bold;

View File

@ -1,20 +0,0 @@
export default {
HOST: "localhost",
PORT: 3000,
API_URL: "http://localhost:3000",
GOOGLE_SHEET_ID: "1p8TDSNlgKC7sm1a_wX44NrkpWEH3-Zey1O2ZjYfPsn4",
APP: {
htmlAttributes: { lang: "en" },
title: "Force Orange",
description: "Le site des bénévoles",
titleTemplate: "Force Orange - %s",
meta: [
{
name: "description",
content: "The best react universal starter boilerplate in the world.",
},
],
},
DEV_JWT_SECRET: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!",
DEV_DISCORD_TOKEN: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!",
}

View File

@ -1,4 +0,0 @@
import defaultConfig from "./default"
import prodConfig from "./prod"
export default __DEV__ ? defaultConfig : { ...defaultConfig, ...prodConfig }

View File

@ -1,14 +0,0 @@
import isNode from "detect-node"
const PROTOCOL = (typeof window !== "undefined" && window?.location?.protocol) || "http:"
const PORT = 4000 + (PROTOCOL === "https:" ? 2 : 0)
const API_URL =
__DEV__ || __LOCAL__ || isNode
? `${PROTOCOL}//localhost:${PORT}`
: `${PROTOCOL}//fo.parisestludique.fr`
export default {
PORT,
HOST: "0.0.0.0",
API_URL,
}

View File

@ -1,14 +0,0 @@
/* Copyright Coplay. All Rights Reserved. Use of this source code is governed by an MIT-style license that can be found in the LICENSE file at https://coplay.org/colicense */
import { NextFunction, Request, Response, Router } from "express"
import * as path from "path"
const certbotRouter: Router = Router()
certbotRouter.use((request: Request, response: Response, _next: NextFunction) => {
const filename = request.originalUrl.replace(/.*\//, "")
const resolvedPath: string = path.resolve(`../certbot/.well-known/acme-challenge/${filename}`)
response.setHeader("Content-Type", "text/html")
return response.sendFile(resolvedPath)
})
export default certbotRouter

View File

@ -23,7 +23,6 @@ import {
DiscordRoleWithoutId, DiscordRoleWithoutId,
} from "../services/discordRoles" } from "../services/discordRoles"
import { getSheet } from "./gsheets/accessors" import { getSheet } from "./gsheets/accessors"
import config from "../config"
let cachedToken: string let cachedToken: string
// let cachedClientId: string // let cachedClientId: string
@ -68,7 +67,7 @@ export async function hasDiscordAccess(): Promise<boolean> {
} }
// export async function discordRegisterCommands(): Promise<void> { // export async function discordRegisterCommands(): Promise<void> {
// if (!__REGISTER_DISCORD_COMMANDS__) { // if (!REGISTER_DISCORD_COMMANDS) {
// return // return
// } // }
@ -98,7 +97,7 @@ export async function hasDiscordAccess(): Promise<boolean> {
export async function discordBot(): Promise<void> { export async function discordBot(): Promise<void> {
try { try {
if (__REGISTER_DISCORD_COMMANDS__) { if (REGISTER_DISCORD_COMMANDS) {
return return
} }
@ -421,7 +420,7 @@ async function getCreds(): Promise<void> {
// cachedClientId = parsedCreds.clientId // cachedClientId = parsedCreds.clientId
cachedGuildId = parsedCreds.guildId cachedGuildId = parsedCreds.guildId
} catch (e: any) { } catch (e: any) {
cachedToken = config.DEV_DISCORD_TOKEN cachedToken = "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!" // DEV_DISCORD_TOKEN
} }
} }
} }

View File

@ -50,11 +50,11 @@ export async function saveLocalDb(
states[name] = state states[name] = state
types[name] = type types[name] = type
const toSave = { states, types } const toSave = { states, types }
const jsonDB = __DEV__ ? JSON.stringify(toSave, null, 4) : JSON.stringify(toSave) const jsonDB = DEV ? JSON.stringify(toSave, null, 4) : JSON.stringify(toSave)
await fs.writeFile(DB_PATH, jsonDB) await fs.writeFile(DB_PATH, jsonDB)
toSave.states = anonimizedDb(toSave.states) toSave.states = anonimizedDb(toSave.states)
const jsonAnonimizedDB = __DEV__ ? JSON.stringify(toSave, null, 4) : JSON.stringify(toSave) const jsonAnonimizedDB = DEV ? JSON.stringify(toSave, null, 4) : JSON.stringify(toSave)
await fs.writeFile(ANONYMIZED_DB_PATH, jsonAnonimizedDB) await fs.writeFile(ANONYMIZED_DB_PATH, jsonAnonimizedDB)
} }
@ -341,10 +341,6 @@ function anonimizedNotifs(v: Volunteer): void {
} else if (v.id % 251 === 0) { } else if (v.id % 251 === 0) {
v.acceptsNotifs = "non" v.acceptsNotifs = "non"
} }
v.pushNotifSubscription =
v.id % 13 === 0
? '{"endpoint":"https://fcm.googleapis.com/fcm/send/f-EAfakedfakedU:APA91fakedfakedzIk-DEglfakedfaked9ugI--ljtfakedfakedfakedfakedfakedfakedP3t-ggU7Afakedfakedfakedkai","expirationTime":null,"keys":{"p256dh":"BEZOJSfakedfakedfakedfakedfakedfakedfakedfakedfakedfakedgYs-cafakedw","auth":"GlMfakedfakedFRg"}}'
: ""
} }
function numberToRand(n: number) { function numberToRand(n: number) {

View File

@ -69,7 +69,7 @@ async function sendMeetingEmail(
const hasMeetingDates = miscList?.[0]?.meetingId !== "" const hasMeetingDates = miscList?.[0]?.meetingId !== ""
if (!hasMeetingDates || firstMeeting === "") { if (!hasMeetingDates || firstMeeting === "") {
if (__DEV__ || apiKey === "") { if (DEV || apiKey === "") {
console.error(`Fake sending meeting email to ${email}`) console.error(`Fake sending meeting email to ${email}`)
} else { } else {
sgMail.setApiKey(apiKey) sgMail.setApiKey(apiKey)
@ -93,7 +93,7 @@ async function sendMeetingEmail(
} }
const { meetingTitle, meetingUrl } = meetingLine const { meetingTitle, meetingUrl } = meetingLine
if (__DEV__ || apiKey === "") { if (DEV || apiKey === "") {
console.error( console.error(
`Fake sending meeting email to ${email} for ${meetingTitle} and url ${meetingUrl}` `Fake sending meeting email to ${email} for ${meetingTitle} and url ${meetingUrl}`
) )

View File

@ -136,8 +136,8 @@ export const volunteerPartialAdd = expressAccessor.add(async (list, body) => {
}) })
async function sendSignUpEmail(email: string, password: string): Promise<void> { async function sendSignUpEmail(email: string, password: string): Promise<void> {
const apiKey = process.env.SENDGRID_API_KEY || "" const apiKey = process.env.SENDGRID_API_KEY || null
if (__DEV__ || apiKey === "") { if (DEV || !apiKey) {
console.error(`Fake sending signup email to ${email} with password ${password}`) console.error(`Fake sending signup email to ${email} with password ${password}`)
} else { } else {
sgMail.setApiKey(apiKey) sgMail.setApiKey(apiKey)
@ -178,7 +178,7 @@ export const volunteerLogin = expressAccessor.get<VolunteerLogin>(async (list, b
) )
const noSuccessfulLogin = !some(tries) const noSuccessfulLogin = !some(tries)
const isDevException = __DEV__ && [1, 508].includes(volunteer.id) // Amélie and Tom E const isDevException = DEV && [1, 508].includes(volunteer.id) // Amélie and Tom E
if (noSuccessfulLogin && !isDevException) { if (noSuccessfulLogin && !isDevException) {
throw Error("Mauvais mot de passe pour cet email") throw Error("Mauvais mot de passe pour cet email")
} }
@ -234,8 +234,8 @@ function generatePassword(): string {
} }
async function sendForgetEmail(email: string, password: string): Promise<void> { async function sendForgetEmail(email: string, password: string): Promise<void> {
const apiKey = process.env.SENDGRID_API_KEY || "" const apiKey = process.env.SENDGRID_API_KEY || null
if (__DEV__ || apiKey === "") { if (DEV || !apiKey) {
console.error(`Fake sending forget email to ${email} with password ${password}`) console.error(`Fake sending forget email to ${email} with password ${password}`)
} else { } else {
sgMail.setApiKey(apiKey) sgMail.setApiKey(apiKey)
@ -265,8 +265,6 @@ export const volunteerAsksSet = expressAccessor.set(async (list, body, id) => {
if (notifChanges.hiddenAsks !== undefined) newVolunteer.hiddenAsks = notifChanges.hiddenAsks if (notifChanges.hiddenAsks !== undefined) newVolunteer.hiddenAsks = notifChanges.hiddenAsks
if (notifChanges.acceptsNotifs !== undefined) if (notifChanges.acceptsNotifs !== undefined)
newVolunteer.acceptsNotifs = notifChanges.acceptsNotifs newVolunteer.acceptsNotifs = notifChanges.acceptsNotifs
if (notifChanges.pushNotifSubscription !== undefined)
newVolunteer.pushNotifSubscription = notifChanges.pushNotifSubscription
return { return {
toDatabase: newVolunteer, toDatabase: newVolunteer,
@ -274,7 +272,6 @@ export const volunteerAsksSet = expressAccessor.set(async (list, body, id) => {
id: newVolunteer.id, id: newVolunteer.id,
firstname: newVolunteer.firstname, firstname: newVolunteer.firstname,
hiddenAsks: newVolunteer.hiddenAsks, hiddenAsks: newVolunteer.hiddenAsks,
pushNotifSubscription: newVolunteer.pushNotifSubscription,
acceptsNotifs: newVolunteer.acceptsNotifs, acceptsNotifs: newVolunteer.acceptsNotifs,
} as VolunteerAsks, } as VolunteerAsks,
} }
@ -483,6 +480,7 @@ function setNewPhoto(id: number, photoData: string, prevFilename: string | undef
const buffer = Buffer.from(base64Data, "base64") const buffer = Buffer.from(base64Data, "base64")
const filename = `${id}.${ext}` const filename = `${id}.${ext}`
const filePath = path.resolve(process.cwd(), `public/photos/${filename}`) const filePath = path.resolve(process.cwd(), `public/photos/${filename}`)
// TODO move picture in cloud storage
if (prevFilename) { if (prevFilename) {
const prevFilePath = path.resolve(process.cwd(), `public/photos/${prevFilename}`) const prevFilePath = path.resolve(process.cwd(), `public/photos/${prevFilename}`)
fs.unlinkSync(prevFilePath) fs.unlinkSync(prevFilePath)

View File

@ -8,14 +8,10 @@ import hpp from "hpp"
import favicon from "serve-favicon" import favicon from "serve-favicon"
import chalk from "chalk" import chalk from "chalk"
import * as http from "http" import * as http from "http"
import * as https from "https"
import * as fs from "fs"
import _ from "lodash"
import devServer from "./devServer" import devServer from "./devServer"
import ssr from "./ssr" import ssr from "./ssr"
import certbotRouter from "../routes/certbot"
import { hasSecret, secure } from "./secure" import { hasSecret, secure } from "./secure"
import { announcementListGet } from "./gsheets/announcements" import { announcementListGet } from "./gsheets/announcements"
import { detailedBoxListGet } from "./gsheets/boxes" import { detailedBoxListGet } from "./gsheets/boxes"
@ -49,12 +45,10 @@ import {
volunteerOnSiteInfo, volunteerOnSiteInfo,
} from "./gsheets/volunteers" } from "./gsheets/volunteers"
import { wishListGet, wishAdd } from "./gsheets/wishes" import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config"
import { notificationsSubscribe, notificationMain } from "./notifications"
import { /* discordRegisterCommands, */ discordBot, hasDiscordAccess } from "./discordBot" import { /* discordRegisterCommands, */ discordBot, hasDiscordAccess } from "./discordBot"
import checkAccess from "./checkAccess" import checkAccess from "./checkAccess"
import { hasGSheetsAccess } from "./gsheets/accessors" import { hasGSheetsAccess } from "./gsheets/accessors"
import { addStatus, showStatusAt } from "./status" import { addStatus } from "./status"
import { import {
miscDiscordInvitation, miscDiscordInvitation,
miscFestivalDateListGet, miscFestivalDateListGet,
@ -64,11 +58,11 @@ import { retexSet } from "./gsheets/retex"
checkAccess() checkAccess()
notificationMain()
// discordRegisterCommands() // discordRegisterCommands()
discordBot() discordBot()
const { PORT } = process.env
const app = express() const app = express()
// Allow receiving big images // Allow receiving big images
@ -81,10 +75,6 @@ app.use(helmet({ contentSecurityPolicy: false }))
app.use(hpp()) app.use(hpp())
// Compress all requests // Compress all requests
app.use(compression()) app.use(compression())
// Https with certbot and Let's Encrypt
if (!__DEV__) {
app.use("/.well-known/acme-challenge", certbotRouter)
}
// Use for http request debug (show errors only) // Use for http request debug (show errors only)
app.use(logger("dev", { skip: (_req, res) => res.statusCode < 400 })) app.use(logger("dev", { skip: (_req, res) => res.statusCode < 400 }))
@ -92,7 +82,7 @@ app.use(favicon(path.resolve(process.cwd(), "public/favicon.ico")))
app.use(express.static(path.resolve(process.cwd(), "public"))) app.use(express.static(path.resolve(process.cwd(), "public")))
// Enable dev-server in development // Enable dev-server in development
if (__DEV__) devServer(app) if (DEV) devServer(app)
app.use(express.json()) app.use(express.json())
app.use(cookieParser()) app.use(cookieParser())
@ -106,126 +96,81 @@ app.get(
} }
) )
const apiRouter = express.Router()
/** /**
* APIs * APIs
*/ */
// Google Sheets API // Google Sheets API
app.get("/GameDetailsUpdate", gameDetailsUpdate) apiRouter.get("/GameDetailsUpdate", gameDetailsUpdate)
app.get("/BoxDetailedListGet", detailedBoxListGet) apiRouter.get("/BoxDetailedListGet", detailedBoxListGet)
app.get("/GameListGet", gameListGet) apiRouter.get("/GameListGet", gameListGet)
app.get("/GamesToGiveListGet", gamesToGiveListGet) apiRouter.get("/GamesToGiveListGet", gamesToGiveListGet)
app.get("/GameTitleOrderCategories", gameTitleOrderCategories) apiRouter.get("/GameTitleOrderCategories", gameTitleOrderCategories)
app.get("/MiscFestivalDateListGet", miscFestivalDateListGet) apiRouter.get("/MiscFestivalDateListGet", miscFestivalDateListGet)
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet) apiRouter.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
app.get("/WishListGet", wishListGet) apiRouter.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd) apiRouter.post("/WishAdd", wishAdd)
app.post("/PostulantAdd", postulantAdd) apiRouter.post("/PostulantAdd", postulantAdd)
// Disabling registration app.post("/VolunteerPartialAdd", volunteerPartialAdd) // Disabling registration apiRouter.post("/VolunteerPartialAdd", volunteerPartialAdd)
app.post("/VolunteerLogin", volunteerLogin) apiRouter.post("/VolunteerLogin", volunteerLogin)
app.post("/VolunteerForgot", volunteerForgot) apiRouter.post("/VolunteerForgot", volunteerForgot)
app.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet) apiRouter.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
// Secured APIs // Secured APIs
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet) apiRouter.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
app.get("/GameWithVolunteersListGet", secure as RequestHandler, gameWithVolunteersListGet) apiRouter.get("/GameWithVolunteersListGet", secure as RequestHandler, gameWithVolunteersListGet)
app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation) apiRouter.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
app.post("/RetexSet", secure as RequestHandler, retexSet) apiRouter.post("/RetexSet", secure as RequestHandler, retexSet)
app.get("/TeamListGet", teamListGet) apiRouter.get("/TeamListGet", teamListGet)
app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId) apiRouter.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId)
app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet) apiRouter.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
app.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet) apiRouter.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet)
app.post( apiRouter.post(
"/VolunteerDetailedKnowledgeListGet", "/VolunteerDetailedKnowledgeListGet",
secure as RequestHandler, secure as RequestHandler,
volunteerDetailedKnowledgeList volunteerDetailedKnowledgeList
) )
app.post("/VolunteerLoanSet", secure as RequestHandler, volunteerLoanSet) apiRouter.post("/VolunteerLoanSet", secure as RequestHandler, volunteerLoanSet)
app.post( apiRouter.post(
"/VolunteerParticipationDetailsSet", "/VolunteerParticipationDetailsSet",
secure as RequestHandler, secure as RequestHandler,
volunteerParticipationDetailsSet volunteerParticipationDetailsSet
) )
app.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet) apiRouter.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet)
app.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet) apiRouter.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet)
app.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet) apiRouter.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
app.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet) apiRouter.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet)
app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet) apiRouter.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet) apiRouter.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
app.get("/VolunteerOnSiteInfo", secure as RequestHandler, volunteerOnSiteInfo) apiRouter.get("/VolunteerOnSiteInfo", secure as RequestHandler, volunteerOnSiteInfo)
// Admin only // Admin only
app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew) apiRouter.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) apiRouter.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
// Push notification subscription app.use("/api", apiRouter)
app.post("/notifications/subscribe", notificationsSubscribe)
// Use React server-side rendering middleware // Use React server-side rendering middleware
app.get("*", ssr) app.get("*", ssr)
/**
* Create HTTP and HTTPS server.
*/
const servers = [{ protocol: "http", server: http.createServer(app) }]
interface Cert {
key: string
cert: string
}
const certPaths: Cert[] = [
{
// Prod
key: "/root/certbot/config/live/fo.parisestludique.fr/privkey.pem",
cert: "/root/certbot/config/live/fo.parisestludique.fr/fullchain.pem",
},
{
// Local
key: "../certbot/key.pem",
cert: "../certbot/cert.pem",
},
]
const validCertPath: Cert | undefined = certPaths.find((certPath: Cert) =>
_.every(certPath, (pemPath: string) => fs.existsSync(pemPath))
)
if (validCertPath) {
const httpsOptions = _.mapValues(validCertPath, (pemPath: string) => fs.readFileSync(pemPath))
servers.push({ protocol: "https", server: https.createServer(httpsOptions, app) })
showStatusAt(6)
} else {
showStatusAt(5)
}
/** /**
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
*/ */
servers.forEach(({ protocol, server }) => {
server.listen(protocol === "http" ? config.PORT : <number>config.PORT + 2)
server.on("error", onError)
server.on("listening", () => onListening(server))
})
/** const server = http.createServer(app)
* Event listener for HTTP server 'error' event.
*/
function onError(error: any) { server.listen(PORT)
server.on("error", (error) => {
if (error) { if (error) {
addStatus("Server listening:", chalk.red(`==> 😭 OMG!!! ${error}`)) addStatus("Server listening:", chalk.red(`==> 😭 OMG!!! ${error}`))
} }
} })
server.on("listening", () => {
/**
* Event listener for HTTP server 'listening' event.
*/
function onListening(server: any) {
const addr = server.address() const addr = server.address()
const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}` const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr?.port}`
addStatus("Server listening:", chalk.green(`${bind}`)) addStatus("Server listening:", chalk.green(`${bind}`))
} })
hasGSheetsAccess().then((hasApiAccess: boolean) => { hasGSheetsAccess().then((hasApiAccess: boolean) => {
if (hasApiAccess) { if (hasApiAccess) {
@ -242,13 +187,6 @@ if (hasSendGridApiAccess) {
addStatus("Emailing:", chalk.blue(`🚧 offline, simulated`)) addStatus("Emailing:", chalk.blue(`🚧 offline, simulated`))
} }
const hasPushNotifAccess = !!process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY
if (hasPushNotifAccess) {
addStatus("Push notif:", chalk.green(`✅ online with a Vapid key`))
} else {
addStatus("Push notif:", chalk.blue(`🚧 offline, simulated`))
}
hasDiscordAccess().then((hasApiAccess: boolean) => { hasDiscordAccess().then((hasApiAccess: boolean) => {
if (hasApiAccess) { if (hasApiAccess) {
addStatus("Discord bot:", chalk.green(`✅ online through discord.js`)) addStatus("Discord bot:", chalk.green(`✅ online through discord.js`))

View File

@ -1,138 +0,0 @@
import { lowerFirst } from "lodash"
import { Request, Response, NextFunction } from "express"
import webpush from "web-push"
import {
Announcement,
AnnouncementWithoutId,
translationAnnouncement,
} from "../services/announcement"
import { translationVolunteer, Volunteer, VolunteerWithoutId } from "../services/volunteers"
import { getSheet } from "./gsheets/accessors"
const publicKey = process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY
const privateKey = process.env.FORCE_ORANGE_PRIVATE_VAPID_KEY
const hasPushAccess = publicKey && privateKey && !__DEV__ && !__LOCAL__
if (hasPushAccess) {
webpush.setVapidDetails("mailto: contact@parisestludique.fr", publicKey, privateKey)
}
type Payload = {
message: string
}
export function sendNotifToVolunteer(volunteer: Volunteer, payload: Payload): void {
if (volunteer.acceptsNotifs !== "oui") {
console.error(`Volunteer refuses notifs`)
} else {
const subscription = JSON.parse(volunteer.pushNotifSubscription)
if (!subscription) {
console.error(`Volunteer has no notif subscription`)
} else if (hasPushAccess) {
const stringifiedPayload = JSON.stringify(payload)
webpush
.sendNotification(subscription, stringifiedPayload)
.then((result) => console.error(result))
.catch((e) => console.error(e.stack))
} else {
console.error(
`Fake sending push notif to ${JSON.stringify(subscription)} of ${JSON.stringify(
payload
)})}`
)
}
}
}
export function sendNotifToSubscription(
subscription: webpush.PushSubscription,
payload: Payload
): void {
if (hasPushAccess) {
const stringifiedPayload = JSON.stringify(payload)
webpush
.sendNotification(subscription, stringifiedPayload)
.then((result) => console.error(result))
.catch((e) => console.error(e.stack))
} else {
console.error(
`Fake sending push notif to ${JSON.stringify(subscription)} of ${JSON.stringify(
payload
)})}`
)
}
}
export function notificationsSubscribe(
request: Request,
response: Response,
_next: NextFunction
): void {
sendNotifToSubscription(request.body, {
message:
"Clique-moi pour voir la gazette de février dans la page Annonces !\nFini les emails, cette notification sera notre seul moyen de te prévenir :)",
})
response.status(200).json({ success: true })
}
export function notificationMain(): void {
setInterval(notifyAboutAnnouncement, 5 * 60 * 1000)
setTimeout(notifyAboutAnnouncement, 60 * 1000)
}
async function notifyAboutAnnouncement(): Promise<void> {
const announcementSheet = await getSheet<AnnouncementWithoutId, Announcement>(
"Announcements",
new Announcement(),
translationAnnouncement
)
const announcementList = await announcementSheet.getList()
if (!announcementList) {
return
}
const toSend = announcementList.find((a) => !a.informedWithNotif)
if (!toSend) {
return
}
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
"Volunteers",
new Volunteer(),
translationVolunteer
)
const volunteerList = await volunteerSheet.getList()
if (!volunteerList) {
return
}
const audience = volunteerList.filter(
(v) =>
v.acceptsNotifs === "oui" &&
(v.active === "oui" || v.active === "peut-etre" || v.active === "à distance")
)
console.error(
`Sending announcement ${toSend.title} to ${audience
.map((v) => `${v.firstname} #${v.id}`)
.join(", ")}`
)
const announceDescription: string =
{
gazette: / - /.test(toSend.title)
? `la ${lowerFirst(toSend.title.replace(/.* - /, ""))}`
: `la gazette ${lowerFirst(toSend.title)}`,
"compte rendu": `le compte-rendu du ${lowerFirst(toSend.title)}`,
}[toSend.type] || toSend.title
audience.forEach((v) => {
sendNotifToVolunteer(v, {
message: `Clique-moi pour voir ${announceDescription}`,
})
})
toSend.informedWithNotif = true
await announcementSheet.set(toSend)
}

View File

@ -31,37 +31,6 @@ export default (
${extractor.getStyleTags()} ${extractor.getStyleTags()}
</head> </head>
<body> <body>
<script>
window.isSubscribed = false;
window.swRegistration = null;
//REGISTER AND UNREGISTER SERVICE WORKER
if ('serviceWorker' in navigator && 'PushManager' in window) {
window.addEventListener('load', function() {
var swPath = '/service-worker.js';
navigator.serviceWorker.register(swPath)
.then(function(registration) {
console.log('Service Worker registered');
window.swRegistration = registration;
registration.pushManager.getSubscription()
.then(function(subscription) {
console.log("subscription", JSON.stringify(subscription));
window.isSubscribed = !(subscription === null);
if (window.isSubscribed) {
console.log('User IS subscribed.');
} else {
console.log('User is NOT subscribed.');
}
});
})
.catch(function(err) {
console.log('Service Worker registration failed: ', err);
});
});
}
</script>
<!-- Insert the router, which passed from server-side --> <!-- Insert the router, which passed from server-side -->
<div id="react-view">${htmlContent}</div> <div id="react-view">${htmlContent}</div>
@ -90,5 +59,5 @@ export default (
} }
// Minify HTML in production // Minify HTML in production
return __DEV__ ? html : minify(html, minifyConfig) return DEV ? html : minify(html, minifyConfig)
} }

View File

@ -3,8 +3,6 @@ import path from "path"
import { constants, promises as fs } from "fs" import { constants, promises as fs } from "fs"
import { verify, sign } from "jsonwebtoken" import { verify, sign } from "jsonwebtoken"
import config from "../config"
type AuthorizedRequest = Request & { headers: { authorization: string } } type AuthorizedRequest = Request & { headers: { authorization: string } }
let cachedSecret: string let cachedSecret: string
@ -57,7 +55,7 @@ async function getSecret() {
const secretContent = await fs.readFile(SECRET_PATH) const secretContent = await fs.readFile(SECRET_PATH)
cachedSecret = secretContent && JSON.parse(secretContent.toString()).secret cachedSecret = secretContent && JSON.parse(secretContent.toString()).secret
} catch (e: any) { } catch (e: any) {
cachedSecret = config.DEV_JWT_SECRET cachedSecret = "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!" // DEV_JWT_SECRET
} }
} }

View File

@ -1,7 +1,5 @@
import axios from "axios" import axios from "axios"
import _ from "lodash" import _ from "lodash"
import config from "../config"
import { axiosConfig } from "./auth" import { axiosConfig } from "./auth"
export type ElementWithId = unknown & { id: number } export type ElementWithId = unknown & { id: number }
@ -26,7 +24,7 @@ export default class ServiceAccessors<
} }
return async (id: number): Promise<ElementGetResponse> => { return async (id: number): Promise<ElementGetResponse> => {
try { try {
const { data } = await axios.get(`${config.API_URL}/${this.elementName}Get`, { const { data } = await axios.get(`${API_URL}/${this.elementName}Get`, {
...axiosConfig, ...axiosConfig,
params: { id }, params: { id },
}) })
@ -51,7 +49,7 @@ export default class ServiceAccessors<
return async (): Promise<ElementListGetResponse> => { return async (): Promise<ElementListGetResponse> => {
try { try {
const { data } = await axios.get( const { data } = await axios.get(
`${config.API_URL}/${this.elementName}ListGet`, `${API_URL}/${this.elementName}ListGet`,
axiosConfig axiosConfig
) )
if (data.error) { if (data.error) {
@ -77,7 +75,7 @@ export default class ServiceAccessors<
const auth = { headers: { Authorization: `Bearer ${jwt}` } } const auth = { headers: { Authorization: `Bearer ${jwt}` } }
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig) const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
const { data } = await axios.get( const { data } = await axios.get(
`${config.API_URL}/${this.elementName}ListGet`, `${API_URL}/${this.elementName}ListGet`,
fullAxiosConfig fullAxiosConfig
) )
if (data.error) { if (data.error) {
@ -107,7 +105,7 @@ export default class ServiceAccessors<
try { try {
const auth = { headers: { Authorization: `Bearer ${jwt}` } } const auth = { headers: { Authorization: `Bearer ${jwt}` } }
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig) const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
const rawData = await axios.get(`${config.API_URL}/${this.elementName}${apiName}`, { const rawData = await axios.get(`${API_URL}/${this.elementName}${apiName}`, {
...fullAxiosConfig, ...fullAxiosConfig,
params, params,
}) })
@ -135,7 +133,7 @@ export default class ServiceAccessors<
return async (volunteerWithoutId: ElementNoId): Promise<ElementGetResponse> => { return async (volunteerWithoutId: ElementNoId): Promise<ElementGetResponse> => {
try { try {
const { data } = await axios.post( const { data } = await axios.post(
`${config.API_URL}/${this.elementName}Add`, `${API_URL}/${this.elementName}Add`,
volunteerWithoutId, volunteerWithoutId,
axiosConfig axiosConfig
) )
@ -160,10 +158,11 @@ export default class ServiceAccessors<
return async (volunteer: Element): Promise<ElementGetResponse> => { return async (volunteer: Element): Promise<ElementGetResponse> => {
try { try {
const { data } = await axios.post( const { data } = await axios.post(
`${config.API_URL}/${this.elementName}Set`, `${API_URL}/${this.elementName}Set`,
volunteer, volunteer,
axiosConfig axiosConfig
) )
if (data.error) { if (data.error) {
throw Error(data.error) throw Error(data.error)
} }
@ -185,7 +184,7 @@ export default class ServiceAccessors<
return async (): Promise<ElementCountGetResponse> => { return async (): Promise<ElementCountGetResponse> => {
try { try {
const { data } = await axios.get( const { data } = await axios.get(
`${config.API_URL}/${this.elementName}CountGet`, `${API_URL}/${this.elementName}CountGet`,
axiosConfig axiosConfig
) )
if (data.error) { if (data.error) {
@ -210,10 +209,10 @@ export default class ServiceAccessors<
} }
return async (...params: InputElements): Promise<ElementGetResponse> => { return async (...params: InputElements): Promise<ElementGetResponse> => {
try { try {
const { data } = await axios.get( const { data } = await axios.get(`${API_URL}/${this.elementName}${apiName}`, {
`${config.API_URL}/${this.elementName}${apiName}`, ...axiosConfig,
{ ...axiosConfig, params } params,
) })
if (data.error) { if (data.error) {
throw Error(data.error) throw Error(data.error)
} }
@ -237,7 +236,7 @@ export default class ServiceAccessors<
return async (...params: InputElements): Promise<ElementGetResponse> => { return async (...params: InputElements): Promise<ElementGetResponse> => {
try { try {
const { data } = await axios.post( const { data } = await axios.post(
`${config.API_URL}/${this.elementName}${apiName}`, `${API_URL}/${this.elementName}${apiName}`,
params, params,
axiosConfig axiosConfig
) )
@ -268,10 +267,10 @@ export default class ServiceAccessors<
try { try {
const auth = { headers: { Authorization: `Bearer ${jwt}` } } const auth = { headers: { Authorization: `Bearer ${jwt}` } }
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig) const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
const { data } = await axios.get( const { data } = await axios.get(`${API_URL}/${this.elementName}${apiName}`, {
`${config.API_URL}/${this.elementName}${apiName}`, ...fullAxiosConfig,
{ ...fullAxiosConfig, params } params,
) })
if (data.error) { if (data.error) {
throw Error(data.error) throw Error(data.error)
} }
@ -300,7 +299,7 @@ export default class ServiceAccessors<
const auth = { headers: { Authorization: `Bearer ${jwt}` } } const auth = { headers: { Authorization: `Bearer ${jwt}` } }
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig) const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
const { data } = await axios.post( const { data } = await axios.post(
`${config.API_URL}/${this.elementName}${apiName}`, `${API_URL}/${this.elementName}${apiName}`,
params, params,
fullAxiosConfig fullAxiosConfig
) )

View File

@ -48,8 +48,6 @@ export class Volunteer implements VolunteerPartial {
password2 = "" password2 = ""
pushNotifSubscription = ""
acceptsNotifs = "" acceptsNotifs = ""
team2022 = 0 team2022 = 0
@ -128,7 +126,6 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
created: "creation", created: "creation",
password1: "passe1", password1: "passe1",
password2: "passe2", password2: "passe2",
pushNotifSubscription: "pushNotifSubscription",
acceptsNotifs: "accepteLesNotifs", acceptsNotifs: "accepteLesNotifs",
team2022: "équipe2022", team2022: "équipe2022",
ok: "OK", ok: "OK",
@ -194,7 +191,6 @@ export const volunteerExample: Volunteer = {
created: new Date(0), created: new Date(0),
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O", password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O", password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
pushNotifSubscription: "",
acceptsNotifs: "", acceptsNotifs: "",
team2022: 0, team2022: 0,
ok: [5, 7, 24, 26, 31, 38, 50, 52, 54, 58], ok: [5, 7, 24, 26, 31, 38, 50, 52, 54, 58],
@ -248,7 +244,6 @@ export interface VolunteerAsks {
id: Volunteer["id"] id: Volunteer["id"]
firstname: Volunteer["firstname"] firstname: Volunteer["firstname"]
hiddenAsks: Volunteer["hiddenAsks"] hiddenAsks: Volunteer["hiddenAsks"]
pushNotifSubscription: Volunteer["pushNotifSubscription"]
acceptsNotifs: Volunteer["acceptsNotifs"] acceptsNotifs: Volunteer["acceptsNotifs"]
} }

View File

@ -19,7 +19,7 @@ interface Arg {
// Use inferred return type for making correctly Redux types // Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const createStore = ({ initialState, url, jwt, id, roles }: Arg = {}) => { const createStore = ({ initialState, url, jwt, id, roles }: Arg = {}) => {
const history = __SERVER__ const history = SSR
? createMemoryHistory({ initialEntries: [url || "/"] }) ? createMemoryHistory({ initialEntries: [url || "/"] })
: createBrowserHistory() : createBrowserHistory()
const store = configureStore({ const store = configureStore({
@ -30,7 +30,7 @@ const createStore = ({ initialState, url, jwt, id, roles }: Arg = {}) => {
...getDefaultMiddleware(), ...getDefaultMiddleware(),
routerMiddleware(history), routerMiddleware(history),
], ],
devTools: __DEV__, devTools: DEV,
}) })
if (jwt && id && roles) { if (jwt && id && roles) {

22
src/types/index.d.ts vendored
View File

@ -1,9 +1,8 @@
declare const __CLIENT__: boolean declare const API_URL: string
declare const __SERVER__: boolean declare const SSR: boolean
declare const __DEV__: boolean declare const DEV: boolean
declare const __LOCAL__: boolean declare const REGISTER_DISCORD_COMMANDS: boolean
declare const __REGISTER_DISCORD_COMMANDS__: boolean declare const TEST: boolean
declare const __TEST__: boolean
declare module "*.svg" declare module "*.svg"
declare module "*.gif" declare module "*.gif"
@ -16,12 +15,11 @@ declare module "*.scss"
declare namespace NodeJS { declare namespace NodeJS {
interface Global { interface Global {
__CLIENT__: boolean API_URL: string
__SERVER__: boolean SSR: boolean
__DEV__: boolean DEV: boolean
__LOCAL__: boolean REGISTER_DISCORD_COMMANDS: boolean
__REGISTER_DISCORD_COMMANDS__: boolean TEST: boolean
__TEST__: boolean
$RefreshReg$: () => void $RefreshReg$: () => void
$RefreshSig$$: () => void $RefreshSig$$: () => void
} }

View File

@ -8,8 +8,7 @@ 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 isLocal = process.env.LOCAL === "true"
const isRegisterDiscordCommands = process.env.REGISTER_DISCORD_COMMANDS === "true"
const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => { const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => {
let loaders: RuleSetUseItem[] = [ let loaders: RuleSetUseItem[] = [
{ {
@ -46,11 +45,10 @@ const getPlugins = (isWeb: boolean) => {
}), }),
// Setting global variables // Setting global variables
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__CLIENT__: isWeb, SSR: !isWeb,
__SERVER__: !isWeb, DEV: isDev,
__DEV__: isDev, REGISTER_DISCORD_COMMANDS: Boolean(process.env.REGISTER_DISCORD_COMMANDS),
__LOCAL__: isLocal, API_URL: JSON.stringify(process.env.API_URL),
__REGISTER_DISCORD_COMMANDS__: isRegisterDiscordCommands,
}), }),
] ]

View File

@ -2212,11 +2212,6 @@
"@types/mime" "*" "@types/mime" "*"
"@types/node" "*" "@types/node" "*"
"@types/serviceworker@^0.0.32":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@types/serviceworker/-/serviceworker-0.0.32.tgz#c262fed4e394f7f3e3787a597f8ecb5c12161626"
integrity sha512-3B1uuyZQ86m9C3BBhNfl4mJs+Whdm1ozPyeLm1Z5hX4cKH0JLPfD9CBFEfdXmCMeNdbcJGfBZXYNXzlrpVrwQg==
"@types/source-list-map@*": "@types/source-list-map@*":
version "0.1.6" version "0.1.6"
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.6.tgz#164e169dd061795b50b83c19e4d3be09f8d3a454" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.6.tgz#164e169dd061795b50b83c19e4d3be09f8d3a454"
@ -2258,13 +2253,6 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc"
integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA== integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==
"@types/web-push@^3.3.2":
version "3.6.3"
resolved "https://registry.yarnpkg.com/@types/web-push/-/web-push-3.6.3.tgz#7698cdeeabd70d1129a6e02bd58af1e985cdfa03"
integrity sha512-v3oT4mMJsHeJ/rraliZ+7TbZtr5bQQuxcgD7C3/1q/zkAj29c8RE0F9lVZVu3hiQe5Z9fYcBreV7TLnfKR+4mg==
dependencies:
"@types/node" "*"
"@types/webpack-bundle-analyzer@^4.4.1": "@types/webpack-bundle-analyzer@^4.4.1":
version "4.6.3" version "4.6.3"
resolved "https://registry.yarnpkg.com/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.3.tgz#53c26f21134ca2e5049fd2af4f2ffbf8dfe87b4f" resolved "https://registry.yarnpkg.com/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.3.tgz#53c26f21134ca2e5049fd2af4f2ffbf8dfe87b4f"
@ -2651,13 +2639,6 @@ agent-base@6:
dependencies: dependencies:
debug "4" debug "4"
agent-base@^7.0.2:
version "7.1.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434"
integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==
dependencies:
debug "^4.3.4"
aggregate-error@^3.0.0: aggregate-error@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@ -2927,16 +2908,6 @@ arrify@^2.0.0:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
asn1.js@^5.3.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
dependencies:
bn.js "^4.0.0"
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
safer-buffer "^2.1.0"
ast-types-flow@^0.0.8: ast-types-flow@^0.0.8:
version "0.0.8" version "0.0.8"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
@ -3221,11 +3192,6 @@ bl@^1.0.0:
readable-stream "^2.3.5" readable-stream "^2.3.5"
safe-buffer "^5.1.1" safe-buffer "^5.1.1"
bn.js@^4.0.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
body-parser@1.20.1: body-parser@1.20.1:
version "1.20.1" version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
@ -6146,13 +6112,6 @@ http-proxy-agent@^4.0.1:
agent-base "6" agent-base "6"
debug "4" debug "4"
http_ece@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-1.1.0.tgz#74780c6eb32d8ddfe9e36a83abcd81fe0cd4fb75"
integrity sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==
dependencies:
urlsafe-base64 "~1.0.0"
https-proxy-agent@^5.0.0: https-proxy-agent@^5.0.0:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
@ -6161,14 +6120,6 @@ https-proxy-agent@^5.0.0:
agent-base "6" agent-base "6"
debug "4" debug "4"
https-proxy-agent@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b"
integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==
dependencies:
agent-base "^7.0.2"
debug "4"
https@^1.0.0: https@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4"
@ -8031,11 +7982,6 @@ mini-css-extract-plugin@^2.7.7:
dependencies: dependencies:
schema-utils "^4.0.0" schema-utils "^4.0.0"
minimalistic-assert@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@ -10026,7 +9972,7 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
is-regex "^1.1.4" is-regex "^1.1.4"
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: "safer-buffer@>= 2.1.2 < 3":
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -11377,11 +11323,6 @@ url-to-options@^1.0.1:
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==
urlsafe-base64@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz#23f89069a6c62f46cf3a1d3b00169cefb90be0c6"
integrity sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@ -11493,17 +11434,6 @@ watchpack@^2.4.0:
glob-to-regexp "^0.4.1" glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2" graceful-fs "^4.1.2"
web-push@^3.4.5:
version "3.6.6"
resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.6.6.tgz#592facb26116187136b0d543768ef85675791ac8"
integrity sha512-SyteEck9fiCskNmPxs/GFhJsZrIyLfRvjWNmcUwULLJyCU0f1oxo2sWTokXA1mDAq9vxk4e4gVcb/8agq73NkQ==
dependencies:
asn1.js "^5.3.0"
http_ece "1.1.0"
https-proxy-agent "^7.0.0"
jws "^4.0.0"
minimist "^1.2.5"
webidl-conversions@^3.0.0: webidl-conversions@^3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"