mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-08 08:34:20 +02:00
🚧 WIP
This commit is contained in:
parent
74685fa741
commit
42c7410bfe
@ -8,7 +8,3 @@ GSHEET_ID=
|
||||
DISCORD_TOKEN=
|
||||
DISCORD_CLIENTID=
|
||||
DISCORD_GUILDID=
|
||||
|
||||
## Notifications
|
||||
FORCE_ORANGE_PUBLIC_VAPID_KEY=
|
||||
FORCE_ORANGE_PRIVATE_VAPID_KEY=
|
11
.eslintrc.js
11
.eslintrc.js
@ -55,11 +55,10 @@ module.exports = {
|
||||
"jsx-a11y/control-has-associated-label": "off",
|
||||
},
|
||||
globals: {
|
||||
__CLIENT__: true,
|
||||
__SERVER__: true,
|
||||
__DEV__: true,
|
||||
__LOCAL__: false,
|
||||
__REGISTER_DISCORD_COMMANDS__: false,
|
||||
__TEST__: false,
|
||||
API_URL: false,
|
||||
SSR: true,
|
||||
DEV: true,
|
||||
REGISTER_DISCORD_COMMANDS: false,
|
||||
TEST: false,
|
||||
},
|
||||
}
|
||||
|
@ -31,6 +31,6 @@ COPY . .
|
||||
## Build the app
|
||||
RUN yarn run build
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["yarn", "start"]
|
||||
CMD ["yarn", "run", "start"]
|
@ -54,7 +54,7 @@ Now the app should be serving at <http://localhost:3000>.
|
||||
## Using Docker
|
||||
|
||||
`docker build . -t force-orange`
|
||||
`docker run -d -p 3000:8080 -t force-orange`
|
||||
`docker run -d -p 3000:3000 -t force-orange`
|
||||
|
||||
### Commands
|
||||
|
||||
|
@ -18,12 +18,11 @@ module.exports = {
|
||||
"<rootDir>/jest/assetMock.ts",
|
||||
},
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
__CLIENT__: true,
|
||||
__SERVER__: false,
|
||||
__LOCAL__: false,
|
||||
__REGISTER_DISCORD_COMMANDS__: false,
|
||||
__TEST__: true,
|
||||
API_URL: "http://localhost:3000",
|
||||
DEV: true,
|
||||
SSR: false,
|
||||
REGISTER_DISCORD_COMMANDS: false,
|
||||
TEST: true,
|
||||
},
|
||||
maxConcurrency: 50,
|
||||
maxWorkers: 1,
|
||||
|
@ -43,9 +43,9 @@
|
||||
"dev": "yarn dev:build && nodemon -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",
|
||||
"local-start": "cross-env LOCAL=true yarn build && node ./public/server",
|
||||
"discord-register": "cross-env REGISTER_DISCORD_COMMANDS=true yarn build && node ./public/server",
|
||||
"start": "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 -r dotenv/config ./public/server",
|
||||
"start": "node -r dotenv/config ./public/server",
|
||||
"build": "run-s build:*",
|
||||
"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",
|
||||
@ -82,8 +82,6 @@
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/lodash": "^4.14.177",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/serviceworker": "^0.0.32",
|
||||
"@types/web-push": "^3.3.2",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"axios": "^0.21.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
@ -127,7 +125,6 @@
|
||||
"redux-thunk": "^2.4.2",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"web-push": "^3.4.5",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
45
scripts/deploy.sh
Normal file
45
scripts/deploy.sh
Normal 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
|
@ -2,7 +2,6 @@ import { RouteConfig, renderRoutes } from "react-router-config"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { ToastContainer } from "react-toastify"
|
||||
|
||||
import config from "../config"
|
||||
// Import your global styles here
|
||||
import "normalize.css/normalize.css"
|
||||
import "react-toastify/dist/ReactToastify.css"
|
||||
@ -17,6 +16,19 @@ interface Route {
|
||||
|
||||
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
|
||||
const App = ({ route, location }: Route): JSX.Element => {
|
||||
if (location.pathname === "/fiches") {
|
||||
@ -26,7 +38,7 @@ const App = ({ route, location }: Route): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet {...config.APP}>
|
||||
<Helmet {...app}>
|
||||
<meta
|
||||
name="viewport"
|
||||
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>
|
||||
<h1 className={styles.siteName}>
|
||||
<a href="/">{config.APP.title}</a>
|
||||
<a href="/">Force Orange</a>
|
||||
</h1>
|
||||
<div className={styles.siteDescription}>{config.APP.description}</div>
|
||||
<div className={styles.siteDescription}>Le site des bénévoles</div>
|
||||
</div>
|
||||
<div className={styles.menuWrapper}>
|
||||
<MainMenu />
|
||||
|
@ -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'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>
|
||||
)
|
||||
}
|
@ -17,7 +17,6 @@ import {
|
||||
} from "./AskParticipationDetails"
|
||||
|
||||
import { OnSiteInfo, fetchFor as fetchForOnSiteInfo } from "./OnSiteInfo"
|
||||
// import { AskPushNotif } from "./AskPushNotif"
|
||||
|
||||
const Asks = (): JSX.Element | null => {
|
||||
const { volunteerAsks } = useAskTools()
|
||||
@ -35,8 +34,6 @@ const Asks = (): JSX.Element | null => {
|
||||
AskHosting(asks, 20)
|
||||
AskParticipationDetails(asks, 22)
|
||||
|
||||
// AskPushNotif(asks, 99)
|
||||
|
||||
const onSiteInfoElement = OnSiteInfo()
|
||||
if (_.isEmpty(asks)) {
|
||||
asks.push(onSiteInfoElement)
|
||||
|
@ -13,14 +13,6 @@
|
||||
@include page-content-wrapper(520px);
|
||||
}
|
||||
|
||||
.pushNotificationsPage {
|
||||
@include page-wrapper-center;
|
||||
}
|
||||
|
||||
.pushNotificationsContent {
|
||||
@include page-content-wrapper;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-bottom: 10px;
|
||||
font-weight: bold;
|
||||
|
@ -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!",
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
import defaultConfig from "./default"
|
||||
import prodConfig from "./prod"
|
||||
|
||||
export default __DEV__ ? defaultConfig : { ...defaultConfig, ...prodConfig }
|
@ -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,
|
||||
}
|
@ -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
|
@ -23,7 +23,6 @@ import {
|
||||
DiscordRoleWithoutId,
|
||||
} from "../services/discordRoles"
|
||||
import { getSheet } from "./gsheets/accessors"
|
||||
import config from "../config"
|
||||
|
||||
let cachedToken: string
|
||||
// let cachedClientId: string
|
||||
@ -68,7 +67,7 @@ export async function hasDiscordAccess(): Promise<boolean> {
|
||||
}
|
||||
|
||||
// export async function discordRegisterCommands(): Promise<void> {
|
||||
// if (!__REGISTER_DISCORD_COMMANDS__) {
|
||||
// if (!REGISTER_DISCORD_COMMANDS) {
|
||||
// return
|
||||
// }
|
||||
|
||||
@ -98,7 +97,7 @@ export async function hasDiscordAccess(): Promise<boolean> {
|
||||
|
||||
export async function discordBot(): Promise<void> {
|
||||
try {
|
||||
if (__REGISTER_DISCORD_COMMANDS__) {
|
||||
if (REGISTER_DISCORD_COMMANDS) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -421,7 +420,7 @@ async function getCreds(): Promise<void> {
|
||||
// cachedClientId = parsedCreds.clientId
|
||||
cachedGuildId = parsedCreds.guildId
|
||||
} catch (e: any) {
|
||||
cachedToken = config.DEV_DISCORD_TOKEN
|
||||
cachedToken = "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!" // DEV_DISCORD_TOKEN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,11 +50,11 @@ export async function saveLocalDb(
|
||||
states[name] = state
|
||||
types[name] = type
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -341,10 +341,6 @@ function anonimizedNotifs(v: Volunteer): void {
|
||||
} else if (v.id % 251 === 0) {
|
||||
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) {
|
||||
|
@ -69,7 +69,7 @@ async function sendMeetingEmail(
|
||||
const hasMeetingDates = miscList?.[0]?.meetingId !== ""
|
||||
|
||||
if (!hasMeetingDates || firstMeeting === "") {
|
||||
if (__DEV__ || apiKey === "") {
|
||||
if (DEV || apiKey === "") {
|
||||
console.error(`Fake sending meeting email to ${email}`)
|
||||
} else {
|
||||
sgMail.setApiKey(apiKey)
|
||||
@ -93,7 +93,7 @@ async function sendMeetingEmail(
|
||||
}
|
||||
const { meetingTitle, meetingUrl } = meetingLine
|
||||
|
||||
if (__DEV__ || apiKey === "") {
|
||||
if (DEV || apiKey === "") {
|
||||
console.error(
|
||||
`Fake sending meeting email to ${email} for ${meetingTitle} and url ${meetingUrl}`
|
||||
)
|
||||
|
@ -136,8 +136,8 @@ export const volunteerPartialAdd = expressAccessor.add(async (list, body) => {
|
||||
})
|
||||
|
||||
async function sendSignUpEmail(email: string, password: string): Promise<void> {
|
||||
const apiKey = process.env.SENDGRID_API_KEY || ""
|
||||
if (__DEV__ || apiKey === "") {
|
||||
const apiKey = process.env.SENDGRID_API_KEY || null
|
||||
if (DEV || !apiKey) {
|
||||
console.error(`Fake sending signup email to ${email} with password ${password}`)
|
||||
} else {
|
||||
sgMail.setApiKey(apiKey)
|
||||
@ -178,7 +178,7 @@ export const volunteerLogin = expressAccessor.get<VolunteerLogin>(async (list, b
|
||||
)
|
||||
|
||||
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) {
|
||||
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> {
|
||||
const apiKey = process.env.SENDGRID_API_KEY || ""
|
||||
if (__DEV__ || apiKey === "") {
|
||||
const apiKey = process.env.SENDGRID_API_KEY || null
|
||||
if (DEV || !apiKey) {
|
||||
console.error(`Fake sending forget email to ${email} with password ${password}`)
|
||||
} else {
|
||||
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.acceptsNotifs !== undefined)
|
||||
newVolunteer.acceptsNotifs = notifChanges.acceptsNotifs
|
||||
if (notifChanges.pushNotifSubscription !== undefined)
|
||||
newVolunteer.pushNotifSubscription = notifChanges.pushNotifSubscription
|
||||
|
||||
return {
|
||||
toDatabase: newVolunteer,
|
||||
@ -274,7 +272,6 @@ export const volunteerAsksSet = expressAccessor.set(async (list, body, id) => {
|
||||
id: newVolunteer.id,
|
||||
firstname: newVolunteer.firstname,
|
||||
hiddenAsks: newVolunteer.hiddenAsks,
|
||||
pushNotifSubscription: newVolunteer.pushNotifSubscription,
|
||||
acceptsNotifs: newVolunteer.acceptsNotifs,
|
||||
} as VolunteerAsks,
|
||||
}
|
||||
@ -483,6 +480,7 @@ function setNewPhoto(id: number, photoData: string, prevFilename: string | undef
|
||||
const buffer = Buffer.from(base64Data, "base64")
|
||||
const filename = `${id}.${ext}`
|
||||
const filePath = path.resolve(process.cwd(), `public/photos/${filename}`)
|
||||
// TODO move picture in cloud storage
|
||||
if (prevFilename) {
|
||||
const prevFilePath = path.resolve(process.cwd(), `public/photos/${prevFilename}`)
|
||||
fs.unlinkSync(prevFilePath)
|
||||
|
@ -8,14 +8,10 @@ import hpp from "hpp"
|
||||
import favicon from "serve-favicon"
|
||||
import chalk from "chalk"
|
||||
import * as http from "http"
|
||||
import * as https from "https"
|
||||
import * as fs from "fs"
|
||||
import _ from "lodash"
|
||||
|
||||
import devServer from "./devServer"
|
||||
import ssr from "./ssr"
|
||||
|
||||
import certbotRouter from "../routes/certbot"
|
||||
import { hasSecret, secure } from "./secure"
|
||||
import { announcementListGet } from "./gsheets/announcements"
|
||||
import { detailedBoxListGet } from "./gsheets/boxes"
|
||||
@ -49,12 +45,10 @@ import {
|
||||
volunteerOnSiteInfo,
|
||||
} from "./gsheets/volunteers"
|
||||
import { wishListGet, wishAdd } from "./gsheets/wishes"
|
||||
import config from "../config"
|
||||
import { notificationsSubscribe, notificationMain } from "./notifications"
|
||||
import { /* discordRegisterCommands, */ discordBot, hasDiscordAccess } from "./discordBot"
|
||||
import checkAccess from "./checkAccess"
|
||||
import { hasGSheetsAccess } from "./gsheets/accessors"
|
||||
import { addStatus, showStatusAt } from "./status"
|
||||
import { addStatus } from "./status"
|
||||
import {
|
||||
miscDiscordInvitation,
|
||||
miscFestivalDateListGet,
|
||||
@ -64,11 +58,11 @@ import { retexSet } from "./gsheets/retex"
|
||||
|
||||
checkAccess()
|
||||
|
||||
notificationMain()
|
||||
|
||||
// discordRegisterCommands()
|
||||
discordBot()
|
||||
|
||||
const { PORT } = process.env
|
||||
|
||||
const app = express()
|
||||
|
||||
// Allow receiving big images
|
||||
@ -81,10 +75,6 @@ app.use(helmet({ contentSecurityPolicy: false }))
|
||||
app.use(hpp())
|
||||
// Compress all requests
|
||||
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)
|
||||
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")))
|
||||
|
||||
// Enable dev-server in development
|
||||
if (__DEV__) devServer(app)
|
||||
if (DEV) devServer(app)
|
||||
|
||||
app.use(express.json())
|
||||
app.use(cookieParser())
|
||||
@ -106,126 +96,81 @@ app.get(
|
||||
}
|
||||
)
|
||||
|
||||
const apiRouter = express.Router()
|
||||
|
||||
/**
|
||||
* APIs
|
||||
*/
|
||||
// Google Sheets API
|
||||
app.get("/GameDetailsUpdate", gameDetailsUpdate)
|
||||
app.get("/BoxDetailedListGet", detailedBoxListGet)
|
||||
app.get("/GameListGet", gameListGet)
|
||||
app.get("/GamesToGiveListGet", gamesToGiveListGet)
|
||||
app.get("/GameTitleOrderCategories", gameTitleOrderCategories)
|
||||
app.get("/MiscFestivalDateListGet", miscFestivalDateListGet)
|
||||
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
|
||||
app.get("/WishListGet", wishListGet)
|
||||
app.post("/WishAdd", wishAdd)
|
||||
app.post("/PostulantAdd", postulantAdd)
|
||||
// Disabling registration app.post("/VolunteerPartialAdd", volunteerPartialAdd)
|
||||
app.post("/VolunteerLogin", volunteerLogin)
|
||||
app.post("/VolunteerForgot", volunteerForgot)
|
||||
app.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
|
||||
apiRouter.get("/GameDetailsUpdate", gameDetailsUpdate)
|
||||
apiRouter.get("/BoxDetailedListGet", detailedBoxListGet)
|
||||
apiRouter.get("/GameListGet", gameListGet)
|
||||
apiRouter.get("/GamesToGiveListGet", gamesToGiveListGet)
|
||||
apiRouter.get("/GameTitleOrderCategories", gameTitleOrderCategories)
|
||||
apiRouter.get("/MiscFestivalDateListGet", miscFestivalDateListGet)
|
||||
apiRouter.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
|
||||
apiRouter.get("/WishListGet", wishListGet)
|
||||
apiRouter.post("/WishAdd", wishAdd)
|
||||
apiRouter.post("/PostulantAdd", postulantAdd)
|
||||
// Disabling registration apiRouter.post("/VolunteerPartialAdd", volunteerPartialAdd)
|
||||
apiRouter.post("/VolunteerLogin", volunteerLogin)
|
||||
apiRouter.post("/VolunteerForgot", volunteerForgot)
|
||||
apiRouter.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
|
||||
|
||||
// Secured APIs
|
||||
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
|
||||
app.get("/GameWithVolunteersListGet", secure as RequestHandler, gameWithVolunteersListGet)
|
||||
app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
|
||||
app.post("/RetexSet", secure as RequestHandler, retexSet)
|
||||
app.get("/TeamListGet", teamListGet)
|
||||
app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId)
|
||||
app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
|
||||
app.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet)
|
||||
app.post(
|
||||
apiRouter.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
|
||||
apiRouter.get("/GameWithVolunteersListGet", secure as RequestHandler, gameWithVolunteersListGet)
|
||||
apiRouter.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
|
||||
apiRouter.post("/RetexSet", secure as RequestHandler, retexSet)
|
||||
apiRouter.get("/TeamListGet", teamListGet)
|
||||
apiRouter.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId)
|
||||
apiRouter.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
|
||||
apiRouter.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet)
|
||||
apiRouter.post(
|
||||
"/VolunteerDetailedKnowledgeListGet",
|
||||
secure as RequestHandler,
|
||||
volunteerDetailedKnowledgeList
|
||||
)
|
||||
app.post("/VolunteerLoanSet", secure as RequestHandler, volunteerLoanSet)
|
||||
app.post(
|
||||
apiRouter.post("/VolunteerLoanSet", secure as RequestHandler, volunteerLoanSet)
|
||||
apiRouter.post(
|
||||
"/VolunteerParticipationDetailsSet",
|
||||
secure as RequestHandler,
|
||||
volunteerParticipationDetailsSet
|
||||
)
|
||||
app.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet)
|
||||
app.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet)
|
||||
app.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
|
||||
app.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet)
|
||||
app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
|
||||
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
|
||||
app.get("/VolunteerOnSiteInfo", secure as RequestHandler, volunteerOnSiteInfo)
|
||||
apiRouter.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet)
|
||||
apiRouter.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet)
|
||||
apiRouter.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
|
||||
apiRouter.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet)
|
||||
apiRouter.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
|
||||
apiRouter.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
|
||||
apiRouter.get("/VolunteerOnSiteInfo", secure as RequestHandler, volunteerOnSiteInfo)
|
||||
|
||||
// Admin only
|
||||
app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew)
|
||||
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
|
||||
apiRouter.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew)
|
||||
apiRouter.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
|
||||
|
||||
// Push notification subscription
|
||||
app.post("/notifications/subscribe", notificationsSubscribe)
|
||||
app.use("/api", apiRouter)
|
||||
|
||||
// Use React server-side rendering middleware
|
||||
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.
|
||||
*/
|
||||
servers.forEach(({ protocol, server }) => {
|
||||
server.listen(protocol === "http" ? config.PORT : <number>config.PORT + 2)
|
||||
server.on("error", onError)
|
||||
server.on("listening", () => onListening(server))
|
||||
})
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server 'error' event.
|
||||
*/
|
||||
const server = http.createServer(app)
|
||||
|
||||
function onError(error: any) {
|
||||
server.listen(PORT)
|
||||
server.on("error", (error) => {
|
||||
if (error) {
|
||||
addStatus("Server listening:", chalk.red(`==> 😭 OMG!!! ${error}`))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server 'listening' event.
|
||||
*/
|
||||
|
||||
function onListening(server: any) {
|
||||
})
|
||||
server.on("listening", () => {
|
||||
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}`))
|
||||
}
|
||||
})
|
||||
|
||||
hasGSheetsAccess().then((hasApiAccess: boolean) => {
|
||||
if (hasApiAccess) {
|
||||
@ -242,13 +187,6 @@ if (hasSendGridApiAccess) {
|
||||
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) => {
|
||||
if (hasApiAccess) {
|
||||
addStatus("Discord bot:", chalk.green(`✅ online through discord.js`))
|
||||
|
@ -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)
|
||||
}
|
@ -31,37 +31,6 @@ export default (
|
||||
${extractor.getStyleTags()}
|
||||
</head>
|
||||
<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 -->
|
||||
<div id="react-view">${htmlContent}</div>
|
||||
|
||||
@ -90,5 +59,5 @@ export default (
|
||||
}
|
||||
|
||||
// Minify HTML in production
|
||||
return __DEV__ ? html : minify(html, minifyConfig)
|
||||
return DEV ? html : minify(html, minifyConfig)
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ import path from "path"
|
||||
import { constants, promises as fs } from "fs"
|
||||
import { verify, sign } from "jsonwebtoken"
|
||||
|
||||
import config from "../config"
|
||||
|
||||
type AuthorizedRequest = Request & { headers: { authorization: string } }
|
||||
|
||||
let cachedSecret: string
|
||||
@ -57,7 +55,7 @@ async function getSecret() {
|
||||
const secretContent = await fs.readFile(SECRET_PATH)
|
||||
cachedSecret = secretContent && JSON.parse(secretContent.toString()).secret
|
||||
} catch (e: any) {
|
||||
cachedSecret = config.DEV_JWT_SECRET
|
||||
cachedSecret = "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!" // DEV_JWT_SECRET
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import axios from "axios"
|
||||
import _ from "lodash"
|
||||
|
||||
import config from "../config"
|
||||
import { axiosConfig } from "./auth"
|
||||
|
||||
export type ElementWithId = unknown & { id: number }
|
||||
@ -26,7 +24,7 @@ export default class ServiceAccessors<
|
||||
}
|
||||
return async (id: number): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(`${config.API_URL}/${this.elementName}Get`, {
|
||||
const { data } = await axios.get(`${API_URL}/${this.elementName}Get`, {
|
||||
...axiosConfig,
|
||||
params: { id },
|
||||
})
|
||||
@ -51,7 +49,7 @@ export default class ServiceAccessors<
|
||||
return async (): Promise<ElementListGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
`${config.API_URL}/${this.elementName}ListGet`,
|
||||
`${API_URL}/${this.elementName}ListGet`,
|
||||
axiosConfig
|
||||
)
|
||||
if (data.error) {
|
||||
@ -77,7 +75,7 @@ export default class ServiceAccessors<
|
||||
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
|
||||
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
|
||||
const { data } = await axios.get(
|
||||
`${config.API_URL}/${this.elementName}ListGet`,
|
||||
`${API_URL}/${this.elementName}ListGet`,
|
||||
fullAxiosConfig
|
||||
)
|
||||
if (data.error) {
|
||||
@ -107,7 +105,7 @@ export default class ServiceAccessors<
|
||||
try {
|
||||
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
|
||||
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,
|
||||
params,
|
||||
})
|
||||
@ -135,7 +133,7 @@ export default class ServiceAccessors<
|
||||
return async (volunteerWithoutId: ElementNoId): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
`${config.API_URL}/${this.elementName}Add`,
|
||||
`${API_URL}/${this.elementName}Add`,
|
||||
volunteerWithoutId,
|
||||
axiosConfig
|
||||
)
|
||||
@ -160,10 +158,11 @@ export default class ServiceAccessors<
|
||||
return async (volunteer: Element): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
`${config.API_URL}/${this.elementName}Set`,
|
||||
`${API_URL}/${this.elementName}Set`,
|
||||
volunteer,
|
||||
axiosConfig
|
||||
)
|
||||
|
||||
if (data.error) {
|
||||
throw Error(data.error)
|
||||
}
|
||||
@ -185,7 +184,7 @@ export default class ServiceAccessors<
|
||||
return async (): Promise<ElementCountGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
`${config.API_URL}/${this.elementName}CountGet`,
|
||||
`${API_URL}/${this.elementName}CountGet`,
|
||||
axiosConfig
|
||||
)
|
||||
if (data.error) {
|
||||
@ -210,10 +209,10 @@ export default class ServiceAccessors<
|
||||
}
|
||||
return async (...params: InputElements): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||
{ ...axiosConfig, params }
|
||||
)
|
||||
const { data } = await axios.get(`${API_URL}/${this.elementName}${apiName}`, {
|
||||
...axiosConfig,
|
||||
params,
|
||||
})
|
||||
if (data.error) {
|
||||
throw Error(data.error)
|
||||
}
|
||||
@ -237,7 +236,7 @@ export default class ServiceAccessors<
|
||||
return async (...params: InputElements): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||
`${API_URL}/${this.elementName}${apiName}`,
|
||||
params,
|
||||
axiosConfig
|
||||
)
|
||||
@ -268,10 +267,10 @@ export default class ServiceAccessors<
|
||||
try {
|
||||
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
|
||||
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
|
||||
const { data } = await axios.get(
|
||||
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||
{ ...fullAxiosConfig, params }
|
||||
)
|
||||
const { data } = await axios.get(`${API_URL}/${this.elementName}${apiName}`, {
|
||||
...fullAxiosConfig,
|
||||
params,
|
||||
})
|
||||
if (data.error) {
|
||||
throw Error(data.error)
|
||||
}
|
||||
@ -300,7 +299,7 @@ export default class ServiceAccessors<
|
||||
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
|
||||
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
|
||||
const { data } = await axios.post(
|
||||
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||
`${API_URL}/${this.elementName}${apiName}`,
|
||||
params,
|
||||
fullAxiosConfig
|
||||
)
|
||||
|
@ -48,8 +48,6 @@ export class Volunteer implements VolunteerPartial {
|
||||
|
||||
password2 = ""
|
||||
|
||||
pushNotifSubscription = ""
|
||||
|
||||
acceptsNotifs = ""
|
||||
|
||||
team2022 = 0
|
||||
@ -128,7 +126,6 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
||||
created: "creation",
|
||||
password1: "passe1",
|
||||
password2: "passe2",
|
||||
pushNotifSubscription: "pushNotifSubscription",
|
||||
acceptsNotifs: "accepteLesNotifs",
|
||||
team2022: "équipe2022",
|
||||
ok: "OK",
|
||||
@ -194,7 +191,6 @@ export const volunteerExample: Volunteer = {
|
||||
created: new Date(0),
|
||||
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||
pushNotifSubscription: "",
|
||||
acceptsNotifs: "",
|
||||
team2022: 0,
|
||||
ok: [5, 7, 24, 26, 31, 38, 50, 52, 54, 58],
|
||||
@ -248,7 +244,6 @@ export interface VolunteerAsks {
|
||||
id: Volunteer["id"]
|
||||
firstname: Volunteer["firstname"]
|
||||
hiddenAsks: Volunteer["hiddenAsks"]
|
||||
pushNotifSubscription: Volunteer["pushNotifSubscription"]
|
||||
acceptsNotifs: Volunteer["acceptsNotifs"]
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ interface Arg {
|
||||
// Use inferred return type for making correctly Redux types
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
const createStore = ({ initialState, url, jwt, id, roles }: Arg = {}) => {
|
||||
const history = __SERVER__
|
||||
const history = SSR
|
||||
? createMemoryHistory({ initialEntries: [url || "/"] })
|
||||
: createBrowserHistory()
|
||||
const store = configureStore({
|
||||
@ -30,7 +30,7 @@ const createStore = ({ initialState, url, jwt, id, roles }: Arg = {}) => {
|
||||
...getDefaultMiddleware(),
|
||||
routerMiddleware(history),
|
||||
],
|
||||
devTools: __DEV__,
|
||||
devTools: DEV,
|
||||
})
|
||||
|
||||
if (jwt && id && roles) {
|
||||
|
22
src/types/index.d.ts
vendored
22
src/types/index.d.ts
vendored
@ -1,9 +1,8 @@
|
||||
declare const __CLIENT__: boolean
|
||||
declare const __SERVER__: boolean
|
||||
declare const __DEV__: boolean
|
||||
declare const __LOCAL__: boolean
|
||||
declare const __REGISTER_DISCORD_COMMANDS__: boolean
|
||||
declare const __TEST__: boolean
|
||||
declare const API_URL: string
|
||||
declare const SSR: boolean
|
||||
declare const DEV: boolean
|
||||
declare const REGISTER_DISCORD_COMMANDS: boolean
|
||||
declare const TEST: boolean
|
||||
|
||||
declare module "*.svg"
|
||||
declare module "*.gif"
|
||||
@ -16,12 +15,11 @@ declare module "*.scss"
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface Global {
|
||||
__CLIENT__: boolean
|
||||
__SERVER__: boolean
|
||||
__DEV__: boolean
|
||||
__LOCAL__: boolean
|
||||
__REGISTER_DISCORD_COMMANDS__: boolean
|
||||
__TEST__: boolean
|
||||
API_URL: string
|
||||
SSR: boolean
|
||||
DEV: boolean
|
||||
REGISTER_DISCORD_COMMANDS: boolean
|
||||
TEST: boolean
|
||||
$RefreshReg$: () => void
|
||||
$RefreshSig$$: () => void
|
||||
}
|
||||
|
@ -8,8 +8,7 @@ import LoadablePlugin from "@loadable/webpack-plugin"
|
||||
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"
|
||||
|
||||
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) => {
|
||||
let loaders: RuleSetUseItem[] = [
|
||||
{
|
||||
@ -46,11 +45,10 @@ const getPlugins = (isWeb: boolean) => {
|
||||
}),
|
||||
// Setting global variables
|
||||
new webpack.DefinePlugin({
|
||||
__CLIENT__: isWeb,
|
||||
__SERVER__: !isWeb,
|
||||
__DEV__: isDev,
|
||||
__LOCAL__: isLocal,
|
||||
__REGISTER_DISCORD_COMMANDS__: isRegisterDiscordCommands,
|
||||
SSR: !isWeb,
|
||||
DEV: isDev,
|
||||
REGISTER_DISCORD_COMMANDS: Boolean(process.env.REGISTER_DISCORD_COMMANDS),
|
||||
API_URL: JSON.stringify(process.env.API_URL),
|
||||
}),
|
||||
]
|
||||
|
||||
|
72
yarn.lock
72
yarn.lock
@ -2212,11 +2212,6 @@
|
||||
"@types/mime" "*"
|
||||
"@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@*":
|
||||
version "0.1.6"
|
||||
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"
|
||||
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":
|
||||
version "4.6.3"
|
||||
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:
|
||||
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:
|
||||
version "3.1.0"
|
||||
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"
|
||||
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:
|
||||
version "0.0.8"
|
||||
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"
|
||||
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:
|
||||
version "1.20.1"
|
||||
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"
|
||||
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:
|
||||
version "5.0.1"
|
||||
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"
|
||||
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:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4"
|
||||
@ -8031,11 +7982,6 @@ mini-css-extract-plugin@^2.7.7:
|
||||
dependencies:
|
||||
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:
|
||||
version "3.1.2"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
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"
|
||||
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:
|
||||
version "1.0.2"
|
||||
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"
|
||||
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:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
|
Loading…
x
Reference in New Issue
Block a user