diff --git a/.env.example b/.env.example index a489cbe..fd84752 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,3 @@ GSHEET_ID= DISCORD_TOKEN= DISCORD_CLIENTID= DISCORD_GUILDID= - -## Notifications -FORCE_ORANGE_PUBLIC_VAPID_KEY= -FORCE_ORANGE_PRIVATE_VAPID_KEY= \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index ede1411..cf399b3 100644 --- a/.eslintrc.js +++ b/.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, }, } diff --git a/Dockerfile b/Dockerfile index e9f73fb..b98922f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,6 @@ COPY . . ## Build the app RUN yarn run build -EXPOSE 8080 +EXPOSE 4000 -CMD ["yarn", "start"] \ No newline at end of file +CMD ["yarn", "run", "start"] \ No newline at end of file diff --git a/README.md b/README.md index 767b4ca..7d9d953 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Now the app should be serving at . ## 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 diff --git a/jest/config.js b/jest/config.js index 23e308f..e87b37b 100644 --- a/jest/config.js +++ b/jest/config.js @@ -18,12 +18,11 @@ module.exports = { "/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, diff --git a/package.json b/package.json index 644778c..8fdced3 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..8a56e40 --- /dev/null +++ b/scripts/deploy.sh @@ -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 diff --git a/src/app/index.tsx b/src/app/index.tsx index f5a25bc..680c35e 100755 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -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 (
- + {

- {config.APP.title} + Force Orange

-
{config.APP.description}
+
Le site des bénévoles
diff --git a/src/components/Asks/AskPushNotif.tsx b/src/components/Asks/AskPushNotif.tsx deleted file mode 100644 index 7c963b9..0000000 --- a/src/components/Asks/AskPushNotif.tsx +++ /dev/null @@ -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) => - setAcceptsNotifs(e.target.value) - - const onSubmit = useCallback(async (): Promise => { - 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, -
- - -
- Enregistrer -
-
- ) -} diff --git a/src/components/Asks/index.tsx b/src/components/Asks/index.tsx index 9bd19a9..ab2fe88 100644 --- a/src/components/Asks/index.tsx +++ b/src/components/Asks/index.tsx @@ -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) diff --git a/src/components/Asks/styles.module.scss b/src/components/Asks/styles.module.scss index dd1a60e..085d8c5 100755 --- a/src/components/Asks/styles.module.scss +++ b/src/components/Asks/styles.module.scss @@ -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; diff --git a/src/config/default.ts b/src/config/default.ts deleted file mode 100755 index 3b4d494..0000000 --- a/src/config/default.ts +++ /dev/null @@ -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!", -} diff --git a/src/config/index.ts b/src/config/index.ts deleted file mode 100755 index dd5c122..0000000 --- a/src/config/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import defaultConfig from "./default" -import prodConfig from "./prod" - -export default __DEV__ ? defaultConfig : { ...defaultConfig, ...prodConfig } diff --git a/src/config/prod.ts b/src/config/prod.ts deleted file mode 100755 index 5c0d841..0000000 --- a/src/config/prod.ts +++ /dev/null @@ -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, -} diff --git a/src/routes/certbot.ts b/src/routes/certbot.ts deleted file mode 100644 index 14cc350..0000000 --- a/src/routes/certbot.ts +++ /dev/null @@ -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 diff --git a/src/server/discordBot.ts b/src/server/discordBot.ts index bdafc00..4aa2228 100644 --- a/src/server/discordBot.ts +++ b/src/server/discordBot.ts @@ -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 { } // export async function discordRegisterCommands(): Promise { -// if (!__REGISTER_DISCORD_COMMANDS__) { +// if (!REGISTER_DISCORD_COMMANDS) { // return // } @@ -98,7 +97,7 @@ export async function hasDiscordAccess(): Promise { export async function discordBot(): Promise { try { - if (__REGISTER_DISCORD_COMMANDS__) { + if (REGISTER_DISCORD_COMMANDS) { return } @@ -421,7 +420,7 @@ async function getCreds(): Promise { // cachedClientId = parsedCreds.clientId cachedGuildId = parsedCreds.guildId } catch (e: any) { - cachedToken = config.DEV_DISCORD_TOKEN + cachedToken = "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!" // DEV_DISCORD_TOKEN } } } diff --git a/src/server/gsheets/localDb.ts b/src/server/gsheets/localDb.ts index 9a2a1f8..f44da22 100644 --- a/src/server/gsheets/localDb.ts +++ b/src/server/gsheets/localDb.ts @@ -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) { diff --git a/src/server/gsheets/postulants.ts b/src/server/gsheets/postulants.ts index d13be81..c8ece93 100644 --- a/src/server/gsheets/postulants.ts +++ b/src/server/gsheets/postulants.ts @@ -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}` ) diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts index 0806909..08bb212 100644 --- a/src/server/gsheets/volunteers.ts +++ b/src/server/gsheets/volunteers.ts @@ -136,8 +136,8 @@ export const volunteerPartialAdd = expressAccessor.add(async (list, body) => { }) async function sendSignUpEmail(email: string, password: string): Promise { - 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(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 { - 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) diff --git a/src/server/index.ts b/src/server/index.ts index 96e32cc..95668fa 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -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 : 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`)) diff --git a/src/server/notifications.ts b/src/server/notifications.ts index 854811a..e69de29 100644 --- a/src/server/notifications.ts +++ b/src/server/notifications.ts @@ -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 { - const announcementSheet = await getSheet( - "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( - "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) -} diff --git a/src/server/renderHtml.ts b/src/server/renderHtml.ts index 85cc6f8..819c940 100755 --- a/src/server/renderHtml.ts +++ b/src/server/renderHtml.ts @@ -31,37 +31,6 @@ export default ( ${extractor.getStyleTags()} - -
${htmlContent}
@@ -90,5 +59,5 @@ export default ( } // Minify HTML in production - return __DEV__ ? html : minify(html, minifyConfig) + return DEV ? html : minify(html, minifyConfig) } diff --git a/src/server/secure.ts b/src/server/secure.ts index 4a5ddf5..6dfe240 100644 --- a/src/server/secure.ts +++ b/src/server/secure.ts @@ -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 } } diff --git a/src/services/accessors.ts b/src/services/accessors.ts index 99ccc82..be89597 100644 --- a/src/services/accessors.ts +++ b/src/services/accessors.ts @@ -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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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 ) diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts index 74dc447..4836eee 100644 --- a/src/services/volunteers.ts +++ b/src/services/volunteers.ts @@ -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"] } diff --git a/src/store/index.ts b/src/store/index.ts index ad2e0bf..2282557 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -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) { diff --git a/src/types/index.d.ts b/src/types/index.d.ts index bfe74ff..742cf11 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -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 } diff --git a/webpack/base.config.ts b/webpack/base.config.ts index 1750fad..1fbf39d 100644 --- a/webpack/base.config.ts +++ b/webpack/base.config.ts @@ -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), }), ] diff --git a/yarn.lock b/yarn.lock index ad39b4b..d9fefd6 100644 --- a/yarn.lock +++ b/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"