From af36a78d86d2790aabeef7e03d42e35328cfde48 Mon Sep 17 00:00:00 2001 From: Petap0w Date: Thu, 3 Feb 2022 12:43:01 +0100 Subject: [PATCH 01/19] Add scanNext --- package.json | 4 + public/base-locales/de.json | 9 +- public/base-locales/en.json | 9 +- public/base-locales/fr.json | 9 +- server/src/configs/default.json | 12 ++ server/src/configs/local.example.json | 4 + server/src/routes/rootRouter.js | 60 ++++++- src/components/Map.jsx | 15 +- src/components/layout/FloatingBtn.jsx | 22 ++- src/components/layout/Nav.jsx | 3 + src/components/layout/dialogs/ScanNext.jsx | 108 ++++++++++++ .../layout/dialogs/ScanNextTarget.jsx | 159 ++++++++++++++++++ 12 files changed, 403 insertions(+), 11 deletions(-) create mode 100644 src/components/layout/dialogs/ScanNext.jsx create mode 100644 src/components/layout/dialogs/ScanNextTarget.jsx diff --git a/package.json b/package.json index fbb775ec1..c7f58a78e 100644 --- a/package.json +++ b/package.json @@ -69,10 +69,14 @@ "@material-ui/styles": "^4.11.4", "@sentry/react": "^6.16.1", "@sentry/tracing": "^6.16.1", + "@turf/boolean-point-in-polygon": "^6.5.0", "@turf/center": "^6.3.0", + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0", "apollo-link-timeout": "^4.0.0", "apollo-server-core": "^3.5.0", "apollo-server-express": "^3.5.0", + "axios": "^0.21.1", "bcrypt": "^5.0.1", "compression": "^1.7.4", "config": "^3.3.6", diff --git a/public/base-locales/de.json b/public/base-locales/de.json index 93bf6d511..6fbb26040 100644 --- a/public/base-locales/de.json +++ b/public/base-locales/de.json @@ -488,5 +488,12 @@ "filters_reset_title": "Filter zurücksetzen", "loading": "laden von {{category}}", "loading_icons": "Icons abrufen", - "loading_invasions": "Rocket-Lineup abrufen" + "loading_invasions": "Rocket-Lineup abrufen", + "scan_next": "Standort scannen", + "click_to_scan": "Hier scannen", + "scan_confirmed_title": "Scan-Anfrage bestätigt", + "scan_confirmed": "Gerät wurde an den Standort geschickt, das Ergebnis wird bald auf der Karte erscheinen!", + "scan_error_title": "Fehler", + "scan_error": "Es ist ein Fehler bei der Verarbeitung der Scan-Anfrage aufgetreten...", + "scan_outside_area": "Dieser Standort liegt außerhalb der Grenzen der zugelassenen Gebiete" } diff --git a/public/base-locales/en.json b/public/base-locales/en.json index d0a320586..34a733221 100644 --- a/public/base-locales/en.json +++ b/public/base-locales/en.json @@ -489,5 +489,12 @@ "filters_reset_title": "Reset filters", "loading": "Loading {{category}}", "loading_icons": "Fetching Icons", - "loading_invasions": "Fetching Invasions" + "loading_invasions": "Fetching Invasions", + "scan_next": "Scan Location", + "click_to_scan": "Scan Here", + "scan_confirmed_title": "Scan demand confirmed", + "scan_confirmed": "Worker has been sent to location, result will soon appear on the map!", + "scan_error_title": "Error", + "scan_error": "There has been an error while processing the scan request...", + "scan_outside_area": "This location is outside the boundaries of authorized areas" } diff --git a/public/base-locales/fr.json b/public/base-locales/fr.json index a6bc1dacf..a3b3a988e 100644 --- a/public/base-locales/fr.json +++ b/public/base-locales/fr.json @@ -482,5 +482,12 @@ "gym_badges_subtitle": "Affiche les badges d'arène sur la carte et une liste dans la page profile.", "loading": "Chargement {{category}}", "loading_icons": "Récupération des Icônes", - "loading_invasions": "Récupération des Invasions" + "loading_invasions": "Récupération des Invasions", + "scan_next": "Scanner un emplacement", + "click_to_scan": "Scanner ici", + "scan_confirmed_title": "Demande de scan confirmée", + "scan_confirmed": "L'appareil a été envoyé à la position de scan, le résultat sera bientôt visible sur la map !", + "scan_error_title": "Erreur", + "scan_error": "Il y a eu une erreur lors du traitement de la demande de scan...", + "scan_outside_area": "Cet emplacement est en dehors des zones autorisées" } \ No newline at end of file diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 43d737197..9fca1ddeb 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -356,6 +356,14 @@ } ] }, + "scanner": { + "apiEndpoint": "http://ip:port/api", + "apiUsername": "username", + "apiPassword": "password", + "enableScanNext": false, + "scanNextInstance": "scanNext", + "scanNextAreaRestriction": [] + }, "webhooks": [], "authentication": { "strategies": [ @@ -470,6 +478,10 @@ "gymBadges": { "enabled": true, "roles": [] + }, + "scanNext": { + "enabled": true, + "roles": [] } } }, diff --git a/server/src/configs/local.example.json b/server/src/configs/local.example.json index 39e2c02a0..859376f85 100644 --- a/server/src/configs/local.example.json +++ b/server/src/configs/local.example.json @@ -223,6 +223,10 @@ "gymBadges": { "enabled": true, "roles": [] + }, + "scanNext": { + "enabled": true, + "roles": [] } } }, diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index 988481a99..ad890e914 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -1,5 +1,6 @@ /* eslint-disable no-console */ const express = require('express') +const Axios = require('axios') const { default: center } = require('@turf/center') const authRouter = require('./authRouter') @@ -48,6 +49,61 @@ rootRouter.get('/area/:area/:zoom?', (req, res) => { } }) +rootRouter.post('/scanNext', async (req, res) => { + try { + if (req.headers['react-map-scanner-secret'] !== 'I need TurtIe\'s help on that') { + console.warn('[scanNext] Incorrect or missing API scanner secret') + return + } + if (!config.scanner.enableScanNext) { + res.status(200).json({ status: 'not activated' }) + } else { + const scanData = req.body + const latRegex = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)$/i + const lonRegex = /^[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/i + if (!scanData.scanNextLocation + || !(latRegex.test(scanData.scanNextLocation[0]) && lonRegex.test(scanData.scanNextLocation[1]))) { + res.status(200).json({ status: 'wrong coordinates' }) + } + const params = { + scan_next: true, + instance_name: config.scanner.scanNextInstance, + lat: scanData.scanNextLocation[0].toFixed(5), + lon: scanData.scanNextLocation[1].toFixed(5), + coords: JSON.stringify(scanData.scanNextCoords.map(coord => ( + { lat: parseFloat(coord[0].toFixed(5)), lon: parseFloat(coord[1].toFixed(5)) }))), + } + console.log(`[scanNext] Request to scan new location by ${scanData.username}${scanData.userId ? ` (${scanData.userId})` : ''} - type ${scanData.scanNextType}: ${scanData.scanNextLocation[0].toFixed(5)},${scanData.scanNextLocation[0].toFixed(5)}`) + Axios.get(`${config.scanner.apiEndpoint}set_data`, { + params, + headers: { + Authorization: `Basic ${Buffer.from(`${config.scanner.apiUsername}:${config.scanner.apiPassword}`).toString('base64')}`, + }, + }).then((response) => { + if (response.data.status === 'ok') { + console.log(`[scanNext] Request from ${scanData.username}${scanData.userId ? ` (${scanData.userId})` : ''} successful`) + res.status(200).json({ status: 'ok' }) + } else { + console.log(`[scanNext] Request from ${scanData.username}${scanData.userId ? ` (${scanData.userId})` : ''} unsuccessful:`, response.data.statusText) + res.status(200).json({ status: 'error' }) + } + }).catch((err) => { + if ([416, 404, 401].includes(err.response?.status) && err.response.statusText) { + console.warn('[scanNext] Scanner didn\'t accept the scan request:', err.response.statusText) + } else if (err.code === 'EHOSTUNREACH') { + console.warn('[scanNext] Scanner didn\'t accept the scan request: wrong API endpoint') + } else { + console.warn('[scanNext] Scanner didn\'t accept the scan request:', err) + } + res.status(200).json({ status: 'error' }) + }) + } + } catch (error) { + res.status(500).json({ error }) + console.warn('[scanNext] Scanner error:', error) + } +}) + rootRouter.get('/settings', async (req, res) => { try { if (config.authentication.alwaysEnabledPerms.length || !config.authMethods.length) { @@ -110,6 +166,8 @@ rootRouter.get('/settings', async (req, res) => { }, manualAreas: config.manualAreas || {}, icons: config.icons, + enableScanNext: config.scanner.enableScanNext, + scanNextAreaRestriction: config.scanner.scanNextAreaRestriction, }, available: {}, } @@ -119,7 +177,7 @@ rootRouter.get('/settings', async (req, res) => { serverSettings.loggedIn = req.user // keys that are being sent to the frontend but are not options - const ignoreKeys = ['map', 'manualAreas', 'limit', 'icons'] + const ignoreKeys = ['map', 'manualAreas', 'limit', 'icons', 'enableScanNext', 'scanNextAreaRestriction'] Object.keys(serverSettings.config).forEach(setting => { try { diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 0379fe2d0..f46559040 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -7,6 +7,7 @@ import { useStatic, useStore } from '@hooks/useStore' import Nav from './layout/Nav' import QueryData from './QueryData' import Webhook from './layout/dialogs/webhooks/Webhook' +import ScanNext from './layout/dialogs/ScanNext' const userSettingsCategory = category => { switch (category) { @@ -30,7 +31,8 @@ const getTileServer = (tileServers, settings, isNight) => { return tileServers[settings.tileServers] || fallbackTs } -export default function Map({ serverSettings: { config: { map: config, tileServers }, Icons, webhooks }, params }) { +export default function Map({ serverSettings: { config: { map: config, tileServers, scanNextAreaRestriction }, + Icons, webhooks }, params }) { Utility.analytics(window.location.pathname) const map = useMap() @@ -51,6 +53,7 @@ export default function Map({ serverSettings: { config: { map: config, tileServe const userSettings = useStore(state => state.userSettings) const [webhookMode, setWebhookMode] = useState(false) + const [scanNextMode, setScanNextMode] = useState(false) const [manualParams, setManualParams] = useState(params) const [lc] = useState(L.control.locate({ position: 'bottomright', @@ -168,6 +171,14 @@ export default function Map({ serverSettings: { config: { map: config, tileServe }) ) } + {scanNextMode && ( + + )}