diff --git a/.gitignore b/.gitignore index 3bff17f3e..898ae876f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ # Config files server/src/configs/* !server/src/configs/default.json -!server/src/configs/config.example.json !server/src/configs/areas.example.json +!server/src/configs/local.example.json .env # Masterfile diff --git a/Dockerfile b/Dockerfile index fc7c78c04..5ab793208 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ## Simple Dockerfile to build ReactMap (main branch) # - Inside the container, the content of this git repo lives in /home/node/ ## You have to mount your configs into the container: -# - mount config.json to /home/node/server/src/configs/config.json +# - mount local.json to /home/node/server/src/configs/local.json # - mount areas.json to /home/node/server/src/configs/areas.json # - Also mount every other configuration file necessary into the according directory. diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 77327dbef..731605d66 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -7,7 +7,7 @@ services: restart: unless-stopped volumes: - ./server/src/configs/areas.json:/home/node/server/src/configs/areas.json - - ./server/src/configs/config.json:/home/node/server/src/configs/config.json + - ./server/src/configs/local.json:/home/node/server/src/local/config.json - ./example.env:/home/node/.env ports: - "9090:8080" diff --git a/package.json b/package.json index 9f2c62e07..8d7fdd0a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactmap", - "version": "1.0.12", + "version": "1.0.13", "description": "React based frontend map.", "main": "index.js", "author": "TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com>", @@ -12,8 +12,9 @@ "dev": "nodemon server/src/index.js --watch server", "build": "yarn create-locales && webpack --env prod", "watch": "webpack --watch --env dev", - "generate": "node server/src/services/generateMasterfile.js", - "create-locales": "node server/src/services/createLocales.js", + "generate": "node server/scripts/generateMasterfile.js", + "create-locales": "node server/scripts/createLocales.js", + "config-migrate": "node server/scripts/configMigration.js", "console": "node --experimental-repl-await ./server/src/console.js", "migrate:make": "knex --knexfile server/knexfile.cjs migrate:make", "migrate:latest": "knex --knexfile server/knexfile.cjs migrate:latest", @@ -73,6 +74,7 @@ "apollo-server-express": "^3.5.0", "bcrypt": "^5.0.1", "compression": "^1.7.4", + "config": "^3.3.6", "discord.js": "^12.5.3", "express": "^4.17.1", "express-mysql-session": "^2.1.6", diff --git a/public/base-locales/de.json b/public/base-locales/de.json index 8e9613330..9316aee56 100644 --- a/public/base-locales/de.json +++ b/public/base-locales/de.json @@ -111,7 +111,7 @@ "next_submission": "Nächster Vorschlag!", "never": "nie", "next_gym": "Nächste Arena", - "s2cells": "S2 Zellen", + "scan_cells": "S2 Zellen", "devices": "Geräte", "use_my_location": "Meinen Standort verwenden", "submit_feedback_title": "Feedback/Fehlerbericht senden", @@ -277,7 +277,7 @@ "pvp_subtitle": "Zeigt PVP-Rang/WP/Level-Informationen für jedes Pokemon", "quests_subtitle": "Zeigt Quest Belohnung und Aufgabeninformationen an", "raids_subtitle": "Zeigt alle Raid-Informationen, einschließlich des Raid-Bosses, der Eier, der Ablaufzeit und des Movesets", - "s2cells_subtitle": "Zeigt an, wann eine S2-Zelle zuletzt von einem Gerät gescannt wurde", + "scan_cells_subtitle": "Zeigt an, wann eine S2-Zelle zuletzt von einem Gerät gescannt wurde", "scanAreas_subtitle": "Zeigt verfügbare Scanbereich Polygone", "spawnpoints_subtitle": "Zeigt Spawnpunkte und deren geschätzte Despawnzeit an", "stats_subtitle": "Zeigt Pokemon-Statistiken und Level an", diff --git a/public/base-locales/en.json b/public/base-locales/en.json index e8a9673f9..9bf53fee1 100644 --- a/public/base-locales/en.json +++ b/public/base-locales/en.json @@ -107,7 +107,7 @@ "next_submission": "Next Submission!", "never": "Never", "next_gym": "Next Gym", - "s2cells": "S2 Cells", + "scan_cells": "Scan Cells", "devices": "Devices", "use_my_location": "Use My Location", "submit_feedback_title": "Submit Feedback/Bug Report", @@ -274,7 +274,7 @@ "pvp_subtitle": "Shows PVP rank/CP/Level info for each Pokémon", "quests_subtitle": "Shows quest reward and task information", "raids_subtitle": "Shows all raid info including the raid boss, eggs, expire time, and moveset", - "s2cells_subtitle": "Shows the last time a S2 cell was scanned by a device", + "scan_cells_subtitle": "Shows the last time a S2 cell was scanned by a device", "scanAreas_subtitle": "Shows available scan areas polygons", "spawnpoints_subtitle": "Shows spawnpoints and their estimated despawn time", "stats_subtitle": "Shows Pokémon stats and levels", diff --git a/public/base-locales/es.json b/public/base-locales/es.json index a984dfb92..957fd32a5 100644 --- a/public/base-locales/es.json +++ b/public/base-locales/es.json @@ -108,7 +108,7 @@ "next_submission": "Sumisión Siguiente!", "never": "Nunca", "next_gym": "Siguiente Gimnasio", - "s2cells": "S2cells", + "scan_cells": "S2cells", "devices": "Aparato", "use_my_location": "Usar Mi Puesto", "submit_feedback_title": "Enviar omentarios o reportar errores", @@ -270,7 +270,7 @@ "pvp_subtitle": "Muestra la información de PVP para cada Pokémon", "quests_subtitle": "Muestra la información de las misiones", "raids_subtitle": "Muestra la información de las Incursiones", - "s2cells_subtitle": "Muestra la última vez que una celda S2 fue escaneada por un dispositivo", + "scan_cells_subtitle": "Muestra la última vez que una celda S2 fue escaneada por un dispositivo", "scanAreas_subtitle": "Muestra los polígonos de áreas de escaneo disponibles", "spawnpoints_subtitle": "Muestra las ubicaciones de los spawns y las horas de expiración", "stats_subtitle": "Muestra las estadísticas de los Pokémon", diff --git a/public/base-locales/fr.json b/public/base-locales/fr.json index c08666d95..8bdfa512a 100644 --- a/public/base-locales/fr.json +++ b/public/base-locales/fr.json @@ -111,7 +111,7 @@ "next_submission": "Prochaine Soumission!", "never": "Jamais", "next_gym": "Prochaine Arène", - "s2cells": "S2cells", + "scan_cells": "S2cells", "devices": "Appareils", "use_my_location": "Utiliser Ma Position", "submit_feedback_title": "Envoyer Commentaire/Rapport de bug", @@ -276,7 +276,7 @@ "pvp_subtitle": "Affiche les infos PVP/PC/Niveau pour chaque Pokemon", "quests_subtitle": "Affiche les récompenses de quêtes et tâches", "raids_subtitle": "Affiche les info de raid y compris le Boss, Oeufs, heure d'expiration et attaques", - "s2cells_subtitle": "Affiche la denière fois ou la cellule S2 à été scannées", + "scan_cells_subtitle": "Affiche la denière fois ou la cellule S2 à été scannées", "scanAreas_subtitle": "Affiche les zones de scan", "spawnpoints_subtitle": "Affiche les points d'apparition et les heure d'expiration estimée", "stats_subtitle": "Affiche les Stats et Niveaux des Pokemon", diff --git a/public/base-locales/nl.json b/public/base-locales/nl.json index 56b624d55..448c0a432 100644 --- a/public/base-locales/nl.json +++ b/public/base-locales/nl.json @@ -107,7 +107,7 @@ "next_submission": "Volgende Inzending!", "never": "Nooit", "next_gym": "Volgende Gym", - "s2cells": "S2 Cells", + "scan_cells": "S2 Cells", "devices": "Apparaten", "use_my_location": "Gebruik Mijn Locatie", "submit_feedback_title": "Feedback/Bug Rapport Indienen", @@ -274,7 +274,7 @@ "pvp_subtitle": "Toont PVP rank/CP/Level informatie voor elke Pokémon", "quests_subtitle": "Toont quest reward en task informatie", "raids_subtitle": "Toont alle raid informatie inclusief de raid boss, eggs, vervaltijd, en moveset", - "s2cells_subtitle": "Toont de laatste tijd een S2 cell was bijgewerkt door een scanner.", + "scan_cells_subtitle": "Toont de laatste tijd een S2 cell was bijgewerkt door een scanner.", "scanAreas_subtitle": "Toont beschikbare scan gebieden polygons", "spawnpoints_subtitle": "Toont spawnpoints en hun geschatte despawntijd", "stats_subtitle": "Toont Pokémon Statistieken en levels", diff --git a/public/base-locales/pl.json b/public/base-locales/pl.json index 286f77374..29ab90def 100644 --- a/public/base-locales/pl.json +++ b/public/base-locales/pl.json @@ -110,7 +110,7 @@ "next_submission": "Kolejne zgłoszenie!", "never": "Nigdy", "next_gym": "Kolejny gym", - "s2cells": "Komórki S2", + "scan_cells": "Komórki S2", "devices": "Urządzenia", "use_my_location": "Użyj bieżącej lokalizacji", "submit_feedback_title": "Prześlij komentarz / Zgłoś błąd", @@ -276,7 +276,7 @@ "pvp_subtitle": "Pokazuje rangę PVP/CP/informację o poziomie każdego Pokemona", "quests_subtitle": "Pokazuje nagrodę oraz informację o zadaniu", "raids_subtitle": "Pokazuje dane raidu, takie jak informacja o bossie, jajku, czasie zakończenia czy atakach bossa", - "s2cells_subtitle": "Pokazuje czas ostatniego skanowania komórki S2 przez urządzenie", + "scan_cells_subtitle": "Pokazuje czas ostatniego skanowania komórki S2 przez urządzenie", "scanAreas_subtitle": "Pokazuje dostępne obszary skanowania", "spawnpoints_subtitle": "Pokazuje spawnpointy oraz ich przybliżone czasy despawnu", "stats_subtitle": "Pokazuje statystyki i poziomy Pokemona", diff --git a/public/images/perms/s2cells.png b/public/images/perms/scanCells.png similarity index 100% rename from public/images/perms/s2cells.png rename to public/images/perms/scanCells.png diff --git a/server/knexfile.cjs b/server/knexfile.cjs index f4fb07aad..6dee22b14 100644 --- a/server/knexfile.cjs +++ b/server/knexfile.cjs @@ -1,18 +1,24 @@ +/* eslint-disable no-console */ const path = require('path') const { database: { schemas, settings: { migrationTableName } } } = require('./src/services/config') const migrationUrl = 'src/db/migrations' -const selectedDb = Object.keys(schemas).find(dbName => schemas[dbName].useFor.includes('user')) || Object.keys(schemas)[0] +const selectedDb = schemas.find(db => db.useFor.includes('user')) + +if (!selectedDb) { + console.warn('No database selected for React Map Tables') + process.exit(9) +} const connection = { client: 'mysql2', connection: { - host: schemas[selectedDb].host, - port: schemas[selectedDb].port, - user: schemas[selectedDb].username, - password: schemas[selectedDb].password, - database: schemas[selectedDb].database, + host: selectedDb.host, + port: selectedDb.port, + user: selectedDb.username, + password: selectedDb.password, + database: selectedDb.database, }, migrations: { tableName: migrationTableName, diff --git a/server/scripts/configMigration.js b/server/scripts/configMigration.js new file mode 100644 index 000000000..be3faa9b8 --- /dev/null +++ b/server/scripts/configMigration.js @@ -0,0 +1,381 @@ +/* eslint-disable no-console */ +const fs = require('fs') +const oldConfig = require('../src/configs/config.json') + +const convertObjToArr = (obj) => obj ? Object.entries(obj).map(([k, v]) => ({ + name: k, + ...v, +})) : undefined + +const convertMapObject = (obj) => ({ + general: { + title: obj?.title, + headerTitle: obj?.headerTitle, + startLat: obj?.startLat, + startLon: obj?.startLon, + startZoom: obj?.startZoom, + minZoom: obj?.minZoom, + maxZoom: obj?.maxZoom, + interactionRangeZoom: obj?.interactionRangeZoom, + }, + localeSelection: obj?.localeSelection, + customRoutes: { + discordAuthUrl: obj?.discordAuthUrl, + telegramAuthUrl: obj?.telegramAuthUrl, + telegramBotEnvRef: obj?.telegramBotEnvRef, + localAuthUrl: obj?.localAuthUrl, + }, + links: { + discordInvite: obj?.discordInvite, + feedbackLink: obj?.feedbackLink, + statsLink: obj?.statsLink, + rolesLinksName: obj?.rolesLinksName, + rolesLink: obj?.rolesLink, + }, + holidayEffects: { + christmasSnow: obj?.christmasSnow, + newYearsFireworks: obj?.newYearsFireworks, + valentinesDay: obj?.valentinesDay, + }, + misc: { + enableMapJsFilter: obj?.legacyPkmnFilter, + questRewardTypeFilters: obj?.questRewardTypeFilters, + fetchLatestInvasions: obj?.fetchLatestInvasions, + invasionCacheHrs: obj?.invasionCacheHrs, + navigationControls: obj?.navigationControls, + forceTutorial: obj?.forceTutorial, + enableTutorial: obj?.enableTutorial, + enableUserProfile: obj?.enableUserProfile, + enableQuestSetSelector: obj?.enableQuestSetSelector, + }, + theme: obj?.theme, + clustering: { + gyms: { + zoomLevel: obj?.clusterZoomLevels?.gyms, + forcedLimit: obj?.clusterZoomLevels?.forcedClusterLimit, + }, + pokestops: { + zoomLevel: obj?.clusterZoomLevels?.pokestops, + forcedLimit: obj?.clusterZoomLevels?.forcedClusterLimit, + }, + pokemon: { + zoomLevel: obj?.clusterZoomLevels?.pokemon, + forcedLimit: obj?.clusterZoomLevels?.forcedClusterLimit, + }, + portals: { + zoomLevel: obj?.clusterZoomLevels?.portals, + forcedLimit: obj?.clusterZoomLevels?.forcedClusterLimit, + }, + spawnpoints: { + zoomLevel: obj?.clusterZoomLevels?.spawnpoints, + forcedLimit: obj?.clusterZoomLevels?.forcedClusterLimit, + }, + }, + messageOfTheDay: obj.messageOfTheDay + ? ensureMotd(obj?.messageOfTheDay) + : undefined, + donationPage: obj?.donationPage, + loginPage: obj?.loginPage, +}) + +const ensureMotd = (obj) => { + if (obj.messages) { + const updateFieldRec = (messages) => messages.map(message => { + if (message.messages) { + message.components = updateFieldRec(message.messages) + delete message.messages + } + return message + }) + obj.components = obj.messages.map(m => { + if (m.type !== 'parent') return m + if (m.messages) { + m.components = m.components || updateFieldRec(m.messages) + delete m.messages + } + return m + }) + delete obj.messages + } + return obj +} + +const mergeAuth = async () => { + let authMethods = await fs.promises.readdir(`${__dirname}/../src/strategies`) + .then(files => files + .filter(file => file !== 'local.js' && file !== 'discord.js' && file !== 'telegram.js')) + if (authMethods?.length) { + authMethods = authMethods.map(file => file.replace('.js', '')) + console.log('Found Custom Auth Methods:', authMethods, '\n', 'You should double check these were migrated correctly!') + } + + const flattenArray = (perm) => ([ + ...oldConfig?.discord?.perms?.[perm]?.roles || [], + ...oldConfig?.telegram?.perms?.[perm]?.roles || [], + ...authMethods.flatMap(m => oldConfig?.[m]?.perms[perm]?.roles || []), + ]) + + const discordObj = (obj, name) => ({ + name, + enabled: obj?.enabled, + type: 'discord', + logChannelId: obj?.logChannelId, + presence: obj?.presence, + presenceType: obj?.presenceType, + botToken: obj?.botToken, + clientId: obj?.clientId, + clientSecret: obj?.clientSecret, + redirectUri: obj?.redirectUri, + allowedGuilds: obj?.allowedGuilds, + blockedGuilds: obj?.blockedGuilds, + allowedUsers: obj?.allowedUsers, + }) + + const telegramObj = (obj, name) => ({ + name, + enabled: obj?.enabled, + type: 'telegram', + botToken: obj?.botToken, + groups: obj?.groups, + }) + + const localObj = (obj, name) => ({ + name, + enabled: obj?.enabled, + type: 'local', + }) + + const checkEnabled = (perm) => oldConfig?.discord?.perms?.[perm]?.enabled + || oldConfig?.telegram?.perms?.[perm]?.enabled + + const baseAuth = { + strategies: [], + areaRestrictions: [ + ...oldConfig?.discord?.areaRestrictions || [], + ...oldConfig?.telegram?.areaRestrictions || [], + ...oldConfig?.local?.areaRestrictions || [], + ...authMethods.flatMap(m => oldConfig?.[m]?.areaRestrictions || []), + ], + excludeFromTutorial: oldConfig?.excludeFromTutorial + ? oldConfig.excludeFromTutorial.map(perm => perm === 's2cells' ? 'scanCells' : perm) + : undefined, + alwaysEnabledPerms: oldConfig?.alwaysEnabledPerms + ? oldConfig.alwaysEnabledPerms.map(perm => perm === 's2cells' ? 'scanCells' : perm) + : undefined, + perms: { + map: { + enabled: checkEnabled('map'), + roles: flattenArray('map'), + }, + pokemon: { + enabled: checkEnabled('pokemon'), + roles: flattenArray('pokemon'), + }, + iv: { + enabled: checkEnabled('iv'), + roles: flattenArray('iv'), + }, + pvp: { + enabled: checkEnabled('pvp'), + roles: flattenArray('pvp'), + }, + gyms: { + enabled: checkEnabled('gyms'), + roles: flattenArray('gyms'), + }, + raids: { + enabled: checkEnabled('raids'), + roles: flattenArray('raids'), + }, + pokestops: { + enabled: checkEnabled('pokestops'), + roles: flattenArray('pokestops'), + }, + quests: { + enabled: checkEnabled('quests'), + roles: flattenArray('quests'), + }, + lures: { + enabled: checkEnabled('lures'), + roles: flattenArray('lures'), + }, + portals: { + enabled: checkEnabled('portals'), + roles: flattenArray('portals'), + }, + submissionCells: { + enabled: checkEnabled('submissionCells'), + roles: flattenArray('submissionCells'), + }, + invasions: { + enabled: checkEnabled('invasions'), + roles: flattenArray('invasions'), + }, + nests: { + enabled: checkEnabled('nests'), + roles: flattenArray('nests'), + }, + scanAreas: { + enabled: checkEnabled('scanAreas'), + roles: flattenArray('scanAreas'), + }, + weather: { + enabled: checkEnabled('weather'), + roles: flattenArray('weather'), + }, + spawnpoints: { + enabled: checkEnabled('spawnpoints'), + roles: flattenArray('spawnpoints'), + }, + scanCells: { + enabled: checkEnabled('scanCells'), + roles: flattenArray('s2cells'), + }, + devices: { + enabled: checkEnabled('devices'), + roles: flattenArray('devices'), + }, + donor: { + enabled: checkEnabled('donor'), + roles: flattenArray('donor'), + }, + }, + } + if (oldConfig?.discord) { + baseAuth.strategies.push(discordObj(oldConfig?.discord, 'discord')) + } + if (oldConfig?.telegram) { + baseAuth.strategies.push(telegramObj(oldConfig?.telegram, 'telegram')) + } + if (oldConfig?.local) { + baseAuth.strategies.push(localObj(oldConfig?.local, 'local')) + if (oldConfig?.local?.perms) { + oldConfig.local.perms.forEach(perm => { + if (perm === 's2cells') { + baseAuth.perms.scanCell.roles.push('local') + } else { + Object.keys(baseAuth.perms).forEach(key => { + if (perm.includes(key)) { + baseAuth.perms[key].roles.push('local') + } + }) + } + }) + } + } + authMethods.forEach(m => { + if (m.toLowerCase().includes('discord')) { + baseAuth.strategies.push(discordObj(oldConfig[m], m)) + } else if (m.toLowerCase().includes('telegram')) { + baseAuth.strategies.push(telegramObj(oldConfig[m], m)) + } else if (m.toLowerCase().includes('local')) { + baseAuth.strategies.push(localObj(oldConfig[m], m)) + } else { + console.warn('Unable to process Auth Method:', m, 'you will need to manually migrate this!') + } + }) + return baseAuth +} + +const rebuildConfig = async () => ({ + interface: oldConfig?.interface, + port: oldConfig?.port, + devOptions: oldConfig?.devOptions, + api: { + ...oldConfig?.api, + pvpMinCp: undefined, + sessionSecret: `${oldConfig?.api?.sessionSecret}x`, + pvp: { + leagues: oldConfig?.database?.settings?.leagues, + levels: oldConfig?.database?.settings?.pvpLevels, + reactMapHandlesPvp: oldConfig?.database?.settings?.reactMapHandlesPvp, + minCp: { + ...oldConfig?.api?.pvpMinCp, + }, + }, + }, + multiDomains: oldConfig.multiDomains + ? Object.entries(oldConfig.multiDomains).map(([domain, values]) => ({ + domain, + ...convertMapObject(values), + })) + : undefined, + map: convertMapObject(oldConfig?.map), + clientSideOptions: oldConfig?.clientSideOptions, + defaultFilters: oldConfig?.defaultFilters ? { + ...oldConfig?.defaultFilters, + pokemon: { + ...oldConfig?.defaultFilters?.pokemon, + globalValues: { + ...oldConfig?.defaultFilters?.pokemon?.globalValues, + pvp: oldConfig?.defaultFilters?.pokemon?.pvpValues, + }, + pvpValues: undefined, + }, + } : undefined, + database: { + settings: { + ...oldConfig?.database?.settings, + leagues: undefined, + reactMapHandlesPvp: undefined, + pvpLevels: undefined, + }, + schemas: Object.values(oldConfig?.database?.schemas).map(s => { + if (s.useFor.includes('s2cell')) { + const s2cellIndex = s.useFor.indexOf('s2cell') + s.useFor[s2cellIndex] = 'scanCell' + } + return s + }), + }, + webhooks: oldConfig?.webhooks, + authentication: await mergeAuth(), + tileServers: convertObjToArr(oldConfig?.tileServers), + navigation: convertObjToArr(oldConfig?.navigation), + icons: oldConfig?.icons, + rarity: oldConfig?.rarity, + manualAreas: convertObjToArr(oldConfig?.manualAreas), +}) + +const cleanConfig = (obj, round) => { + Object.entries(obj).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(subObj => { + if (typeof subObj === 'object') { + cleanConfig(subObj, round) + } + }) + } else if (typeof value === 'object') { + if (!Object.keys(value).length || Object.values(value).every(v => v === undefined)) { + delete obj[key] + console.log('Removed empty object:', key, round) + } else { + cleanConfig(value, round) + } + } + }) +} + +const migrator = async () => { + const config = await rebuildConfig() + cleanConfig(config, '(1)') + cleanConfig(config, '(2)') + if (config?.tileServers?.length === 0) { + delete config.tileServers + } + if (config?.navigation?.length === 0) { + delete config.navigation + } + fs.writeFileSync( + `${__dirname}/../src/configs/local.json`, + JSON.stringify(config, null, 2), + 'utf8', + () => { }, + ) +} + +module.exports.migrator = migrator + +if (require.main === module) { + migrator().then(() => console.log('Migrated Config')) +} diff --git a/server/src/console.js b/server/scripts/console.js similarity index 58% rename from server/src/console.js rename to server/scripts/console.js index 82b84e5d1..eb15cff7f 100644 --- a/server/src/console.js +++ b/server/scripts/console.js @@ -1,6 +1,6 @@ const repl = require('repl') -require('./db/initialization') -const models = require('./models/index') +require('../src/db/initialization') +const models = require('../src/models/index') const replServer = repl.start({ prompt: '> ', diff --git a/server/src/services/createLocales.js b/server/scripts/createLocales.js similarity index 91% rename from server/src/services/createLocales.js rename to server/scripts/createLocales.js index ce262e1d0..a6e816f1f 100644 --- a/server/src/services/createLocales.js +++ b/server/scripts/createLocales.js @@ -2,10 +2,10 @@ const fs = require('fs') const path = require('path') -const fetchJson = require('./api/fetchJson') +const fetchJson = require('../src/services/api/fetchJson') -const appLocalesFolder = path.resolve(__dirname, '../../../public/base-locales') -const finalLocalesFolder = path.resolve(__dirname, '../../../public/locales') +const appLocalesFolder = path.resolve(__dirname, '../../public/base-locales') +const finalLocalesFolder = path.resolve(__dirname, '../../public/locales') const locales = async () => { const localTranslations = await fs.promises.readdir(appLocalesFolder) diff --git a/server/src/services/generateMasterfile.js b/server/scripts/generateMasterfile.js similarity index 71% rename from server/src/services/generateMasterfile.js rename to server/scripts/generateMasterfile.js index a3cee2580..ff8b50d65 100644 --- a/server/src/services/generateMasterfile.js +++ b/server/scripts/generateMasterfile.js @@ -2,16 +2,16 @@ /* eslint-disable no-restricted-syntax */ const fs = require('fs') -const fetchJson = require('./api/fetchJson') -const defaultRarity = require('../data/defaultRarity.json') +const fetchJson = require('../src/services/api/fetchJson') +const defaultRarity = require('../src/data/defaultRarity.json') const getRarityLevel = (id, pkmn) => { - const adminRarity = fs.existsSync('../configs/config.json') - ? JSON.parse(fs.readFileSync('server/src/configs/config.json', 'utf8')) - : JSON.parse(fs.readFileSync('server/src/configs/default.json', 'utf8')) + const adminRarity = fs.existsSync(`${__dirname}/../src/configs/local.json`) + ? JSON.parse(fs.readFileSync(`${__dirname}/../src/configs/local.json`, 'utf8')) + : JSON.parse(fs.readFileSync(`${__dirname}/../src/configs/default.json`, 'utf8')) let rarity for (const [tier, pokemon] of Object.entries(defaultRarity)) { - if (adminRarity.rarity[tier].length > 0) { + if (adminRarity?.rarity?.[tier]?.length) { if (adminRarity.rarity[tier].includes((parseInt(id)))) { rarity = tier } @@ -36,13 +36,13 @@ const generate = async () => { }) fs.writeFile( - 'server/src/data/masterfile.json', + `${__dirname}/../src/data/masterfile.json`, JSON.stringify(masterfile, null, 2), 'utf8', () => { }, ) } catch (e) { - console.warn('Unable to generate new masterfile, using existing.') + console.warn('Unable to generate new masterfile, using existing.', e) } } diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 6088402ba..d6c7b932a 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -1,14 +1,6 @@ { "interface": "0.0.0.0", "port": 8080, - "localeSelection": [ - "en", - "de", - "pl", - "es", - "fr", - "nl" - ], "devOptions": { "enabled": false, "graphiql": false, @@ -23,72 +15,118 @@ "time": 60, "requests": 1000 }, - "pvpMinCp": { - "great": 1400, - "ultra": 2400 - }, "queryAvailable": { - "pokemon": true, - "quests": false, - "raids": false, - "nests": false + "pokemon": false, + "quest": true, + "raid": true, + "nest": false + }, + "pvp": { + "leagues": [ + { + "name": "great", + "cp": 1500 + }, + { + "name": "ultra", + "cp": 2500 + } + ], + "levels": [ + 50, + 51 + ], + "reactMapHandlesPvp": false, + "minCp": { + "great": 1400, + "ultra": 2400 + } }, "portalUpdateLimit": 30, "weatherCellLimit": 3, - "searchResultsLimit": 15 + "searchResultsLimit": 15, + "nestHemisphere": "north" }, - "multiDomains": {}, + "multiDomains": [], "map": { - "title": "ReactMap", - "headerTitle": "ReactMap", - "nestHemisphere": "north", - "enableFeedback": false, - "feedbackLink": "https://forms.gle/wKqWRs9Z7XEAPB7AA", - "enableStats": false, - "statsLink": "https://rdmopole2link.net", - "rolesLinkName": "Role Upgrading Info", - "rolesLink": "https://discordsomething.net", - "forceTutorial": true, - "enableTutorial": true, - "enableUserProfile": true, - "startLat": 0, - "startLon": 0, - "startZoom": 12, - "minZoom": 10, - "maxZoom": 18, - "noScanAreaOverlay": false, - "interactionRangeZoom": 15, - "scanAreasZoom": 12, - "scanCellsZoom": 13, - "submissionZoom": 15, - "legacyPkmnFilter": true, - "enableQuestRewardTypeFilters": false, - "enableQuestSetSelector": false, - "fetchLatestInvasions": true, - "invasionCacheHrs": 1, - "questMessage": "", - "navigationControls": "react", - "discordInvite": "", - "discordAuthUrl": "/auth/discord/callback", - "telegramAuthUrl": "/auth/telegram/callback", - "telegramBotEnvRef": "TELEGRAM_BOT_NAME", - "localAuthUrl": "/auth/local/callback", + "general": { + "title": "ReactMap", + "headerTitle": "ReactMap", + "startLat": 0, + "startLon": 0, + "startZoom": 12, + "minZoom": 10, + "maxZoom": 18, + "interactionRangeZoom": 15, + "scanAreasZoom": 12, + "scanCellsZoom": 13, + "submissionZoom": 15 + }, + "localeSelection": [ + "en", + "de", + "pl", + "es", + "fr", + "nl" + ], + "customRoutes": { + "discordAuthUrl": "/auth/discord/callback", + "telegramAuthUrl": "/auth/telegram/callback", + "telegramBotEnvRef": "TELEGRAM_BOT_NAME", + "localAuthUrl": "/auth/local/callback" + }, + "links": { + "discordInvite": "", + "feedbackLink": "", + "statsLink": "", + "rolesLinkName": "Role Upgrading Info", + "rolesLink": "" + }, + "holidayEffects": { + "christmasSnow": true, + "newYearsFireworks": true, + "valentinesDay": true + }, + "misc": { + "enableMapJsFilter": true, + "questRewardTypeFilters": false, + "fetchLatestInvasions": true, + "invasionCacheHrs": 1, + "questMessage": "", + "navigationControls": "react", + "forceTutorial": true, + "enableTutorial": true, + "enableUserProfile": true, + "enableQuestSetSelector": false + }, "theme": { "style": "dark", "primary": "#ff5722", "secondary": "#00b0ff", "drawer": "temporary" }, - "christmasSnow": true, - "newYearsFireworks": true, - "valentinesDay": true, - "clusterZoomLevels": { - "forcedClusterLimit": 2500, - "gyms": 13, - "pokestops": 14, - "pokemon": 15, - "portals": 14, - "spawnpoints": 12 + "clustering": { + "gyms": { + "zoomLevel": 13, + "forcedLimit": 2500 + }, + "pokestops": { + "zoomLevel": 14, + "forcedLimit": 2500 + }, + "pokemon": { + "zoomLevel": 15, + "forcedLimit": 3000 + }, + "portals": { + "zoomLevel": 14, + "forcedLimit": 3000 + }, + "spawnpoints": { + "zoomLevel": 12, + "forcedLimit": 5000 + } }, "messageOfTheDay": { "index": 0, @@ -244,12 +282,12 @@ "sta_iv": [ 0, 15 + ], + "pvp": [ + 1, + 10 ] - }, - "pvpValues": [ - 1, - 10 - ] + } }, "portals": { "enabled": false @@ -275,14 +313,11 @@ "userTableName": "users", "sessionTableName": "session", "migrationTableName": "knex_migrations", - "leagues": [], - "reactMapHandlesPvp": false, - "pvpLevels": [], - "hideOldQuests": true, + "hideOldQuests": false, "maxConnections": 10 }, - "schemas": { - "scanner": { + "schemas": [ + { "type": "rdm", "host": "127.0.0.1", "port": 3306, @@ -290,11 +325,10 @@ "password": "pass123!", "database": "rdmdb", "charset": "utf8mb4", - "arScanColumn": false, "hasAltQuests": false, "useFor": [] }, - "manual": { + { "type": "manual", "host": "127.0.0.1", "port": 3306, @@ -302,27 +336,44 @@ "password": "pass123!", "database": "manualdb", "charset": "utf8mb4", - "arScanColumn": false, "useFor": [] } - } + ] }, "webhooks": [], - "excludeFromTutorial": [], - "alwaysEnabledPerms": [], - "discord": { - "enabled": false, - "logChannelId": "", - "presence": "Map Status: Online", - "presenceType": 3, - "botToken": "", - "clientId": "", - "clientSecret": "", - "redirectUri": "http://localhost:8080/auth/discord/callback", - "allowedGuilds": [], - "blockedGuilds": [], - "allowedUsers": [], + "authentication": { + "strategies": [ + { + "name": "discord", + "type": "discord", + "enabled": false, + "logChannelId": "", + "presence": "Map Status: Online", + "presenceType": 3, + "botToken": "", + "clientId": "", + "clientSecret": "", + "redirectUri": "http://localhost:8080/auth/discord/callback", + "allowedGuilds": [], + "blockedGuilds": [], + "allowedUsers": [] + }, + { + "name": "telegram", + "type": "telegram", + "enabled": false, + "botToken": "", + "groups": [] + }, + { + "name": "local", + "type": "local", + "enabled": false + } + ], "areaRestrictions": [], + "excludeFromTutorial": [], + "alwaysEnabledPerms": [], "perms": { "map": { "enabled": true, @@ -332,10 +383,6 @@ "enabled": true, "roles": [] }, - "stats": { - "enabled": true, - "roles": [] - }, "iv": { "enabled": true, "roles": [] @@ -392,95 +439,7 @@ "enabled": true, "roles": [] }, - "s2cells": { - "enabled": true, - "roles": [] - }, - "devices": { - "enabled": true, - "roles": [] - }, - "donor": { - "enabled": true, - "roles": [] - } - } - }, - "telegram": { - "enabled": false, - "botToken": "", - "groups": [], - "areaRestrictions": [], - "perms": { - "map": { - "enabled": true, - "roles": [] - }, - "gyms": { - "enabled": true, - "roles": [] - }, - "raids": { - "enabled": true, - "roles": [] - }, - "nests": { - "enabled": true, - "roles": [] - }, - "pokestops": { - "enabled": true, - "roles": [] - }, - "quests": { - "enabled": true, - "roles": [] - }, - "lures": { - "enabled": true, - "roles": [] - }, - "invasions": { - "enabled": true, - "roles": [] - }, - "pokemon": { - "enabled": true, - "roles": [] - }, - "stats": { - "enabled": true, - "roles": [] - }, - "iv": { - "enabled": true, - "roles": [] - }, - "pvp": { - "enabled": true, - "roles": [] - }, - "portals": { - "enabled": true, - "roles": [] - }, - "submissionCells": { - "enabled": true, - "roles": [] - }, - "scanAreas": { - "enabled": true, - "roles": [] - }, - "weather": { - "enabled": true, - "roles": [] - }, - "s2cells": { - "enabled": true, - "roles": [] - }, - "spawnpoints": { + "scanCells": { "enabled": true, "roles": [] }, @@ -494,47 +453,65 @@ } } }, - "local": { - "enabled": false, - "perms": [], - "areaRestrictions": [] - }, - "tileServers": { - "Default": { + "tileServers": [ + { + "name": "auto" + }, + { + "name": "Carto", "attribution": "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.", "url": "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png", "style": "light" }, - "OSM": { + { + "name": "OSM", "attribution": "Map data © OpenStreetMap contributors", "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "style": "light" }, - "DarkMatter": { + { + "name": "Dark Matter", "attribution": "© OpenStreetMap contributors © CARTO", "url": "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", "style": "dark" }, - "Satellite": { + { + "name": "Satellite", "attribution": "© Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community", "url": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", "style": "dark" + }, + { + "name": "AlidadeSmoothDark", + "attribution": "© Stadia Maps, © OpenMapTiles © OpenStreetMap contributors", + "url": "https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png", + "style": "dark" + }, + { + "name": "ThunderForest", + "attribution": "© Thunderforest, © OpenStreetMap contributors", + "url": "https://{s}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png", + "style": "dark" } - }, - "navigation": { - "GoogleMaps": { + ], + "navigation": [ + { + "name": "Google Maps", "url": "https://www.google.com/maps/place/{x},{y}" }, - "AppleMaps": { + { + "name": "Apple Maps", "url": "https://maps.apple.com/maps?daddr={x},{y}" }, - "Waze": { + { + "name": "Waze", "url": "https://www.waze.com/ul?ll={x},{y}" }, - "Intel": { + { + "name": "Intel", "url": "https://intel.ingress.com/intel?pll={x},{y}" } - }, + ], "icons": { "Read_More_Here": "https://github.com/WatWowMap/ReactMap/wiki/Icons-Configuration", "customizable": [], @@ -606,5 +583,5 @@ "regional": [], "event": [] }, - "manualAreas": {} + "manualAreas": [] } \ No newline at end of file diff --git a/server/src/configs/config.example.json b/server/src/configs/local.example.json similarity index 54% rename from server/src/configs/config.example.json rename to server/src/configs/local.example.json index 6fef4fdb3..b7fbb2f2a 100644 --- a/server/src/configs/config.example.json +++ b/server/src/configs/local.example.json @@ -2,55 +2,84 @@ "interface": "0.0.0.0", "port": 8080, "api": { - "sessionSecret": "98ki^e72~!@#(85o3kXLI*#c9wu5l!Z", + "sessionSecret": "98ki^e72~!@#(85o3kXLI*#c9wu5l!Zx", "reactMapSecret": "You Should Change Me", "maxSessions": 5, "queryAvailable": { "pokemon": true, - "quests": true, - "raids": true, - "nests": false + "quest": true, + "raid": true, + "nest": false + }, + "pvp": { + "leagues": [ + { + "name": "great", + "cp": 1500 + }, + { + "name": "ultra", + "cp": 2500 + } + ], + "levels": [ + 50, + 51 + ], + "reactMapHandlesPvp": false } }, "map": { - "title": "ReactMap", - "headerTitle": "ReactMap", - "nestHemisphere": "north", - "style": "dark", - "enableFeedback": true, - "feedbackLink": "https://forms.gle/wKqWRs9Z7XEAPB7AA", - "enableStats": true, - "statsLink": "https://rdmopole2link.net", - "startLat": 0, - "startLon": 0, - "startZoom": 12, - "minZoom": 10, - "maxZoom": 18, - "enableQuestSetSelector": true + "general": { + "title": "ReactMap", + "headerTitle": "ReactMap", + "startLat": 0, + "startLon": 0, + "startZoom": 12, + "minZoom": 10, + "maxZoom": 18 + }, + "links": { + "feedbackLink": "", + "statsLink": "" + }, + "misc": { + "enableQuestSetSelector": true + } }, "clientSideOptions": { "pokemon": { "clustering": true, "glow": [ - { "name": "Hundo", "perm": "iv", "num": 100, "value": "#ff1744", "op": "=" }, - { "name": "Top 3 Ranks", "perm": "pvp", "num": 3, "value": "#0000ff", "op": "<=" }, - { "name": "Multiple", "perm": "pvp", "value": "#800080" } + { + "name": "Hundo", + "perm": "iv", + "num": 100, + "value": "#ff1744", + "op": "=" + }, + { + "name": "Top 3 Ranks", + "perm": "pvp", + "num": 3, + "value": "#0000ff", + "op": "<=" + }, + { + "name": "Multiple", + "perm": "pvp", + "value": "#800080" + } ] } }, "database": { "settings": { "userTableName": "users", - "sessionTableName": "session", - "leagues": [ - { "name": "great", "cp": 1500 }, - { "name": "ultra", "cp": 2500 } - ], - "reactMapHandlesPvp": false, - "pvpLevels": [50, 51] + "sessionTableName": "session" }, - "schemas": { - "scanner": { + "schemas": [ + { "type": "rdm/chuck/cdc/mad/manual", "host": "127.0.0.1", "port": 3306, @@ -58,10 +87,18 @@ "password": "pass123!", "database": "rdmdb", "charset": "utf8mb4", - "arScanColumn": false, - "useFor": ["device", "gym", "pokemon", "pokestop", "s2cell", "spawnpoint", "weather"] + "hasAltQuests": false, + "useFor": [ + "device", + "gym", + "pokemon", + "pokestop", + "scanCell", + "spawnpoint", + "weather" + ] }, - "manual": { + { "type": "manual", "host": "127.0.0.1", "port": 3306, @@ -69,25 +106,33 @@ "password": "pass123!", "database": "manual_1", "charset": "utf8mb4", - "arScanColumn": false, - "useFor": ["session", "user", "nest", "portal"] + "useFor": [ + "session", + "user", + "nest", + "portal" + ] } - } + ] }, - "alwaysEnabledPerms": [], - "discord": { - "enabled": false, - "logChannelId": "", - "presence": "Map Status: Online", - "presenceType": 3, - "botToken": "", - "clientId": "", - "clientSecret": "", - "redirectUri": "http://localhost:8080/auth/discord/callback", - "inviteLink": "", - "allowedGuilds": [], - "blockedGuilds": [], - "allowedUsers": [], + "authentication": { + "strategies": [ + { + "enabled": false, + "type": "discord", + "name": "discord", + "logChannelId": "", + "presence": "Map Status: Online", + "presenceType": 3, + "botToken": "", + "clientId": "", + "clientSecret": "", + "redirectUri": "http://localhost:8080/auth/discord/callback", + "allowedGuilds": [], + "blockedGuilds": [], + "allowedUsers": [] + } + ], "areaRestrictions": [ { "roles": [], @@ -98,6 +143,7 @@ "areas": [] } ], + "alwaysEnabledPerms": [], "perms": { "map": { "enabled": true, @@ -107,10 +153,6 @@ "enabled": true, "roles": [] }, - "stats": { - "enabled": true, - "roles": [] - }, "iv": { "enabled": true, "roles": [] @@ -167,28 +209,47 @@ "enabled": true, "roles": [] }, - "s2cells": { - "enabled": true, + "scanCells": { "roles": [] }, "devices": { "enabled": true, "roles": [] + }, + "donor": { + "roles": [] } } }, - "tileServers": { - "AlidadeSmoothDark": { - "attribution": "© Stadia Maps, © OpenMapTiles © OpenStreetMap contributors", - "url": "https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png", + "tileServers": [ + { + "name": "auto" + }, + { + "name": "Carto", + "attribution": "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.", + "url": "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png", + "style": "light" + }, + { + "name": "OSM", + "attribution": "Map data © OpenStreetMap contributors", + "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "style": "light" + }, + { + "name": "Dark Matter", + "attribution": "© OpenStreetMap contributors © CARTO", + "url": "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", "style": "dark" }, - "ThunderForest": { - "attribution": "© Thunderforest, © OpenStreetMap contributors", - "url": "https://{s}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png", + { + "name": "Satellite", + "attribution": "© Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community", + "url": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", "style": "dark" } - }, + ], "icons": { "Read_More_Here": "https://github.com/WatWowMap/ReactMap/wiki/Icons-Configuration", "customizable": [ @@ -250,15 +311,20 @@ } ] }, - "manualAreas": { - "New York": { + "manualAreas": [ + { + "name": "New York", "lat": 40.7481666, - "lon": -74.0174788 + "lon": -74.0174788, + "zoom": 15 }, - "San Francisco": { + { + "name": "San Francisco", "lat": 37.79539194255634, "lon": -122.39333173075096 }, - "Remove these if you'd prefer the map to read directly from your Geojson": "" - } + { + "name": "Remove all of these if you'd prefer the map to read directly from your Geojson" + } + ] } \ No newline at end of file diff --git a/server/src/db/initialization.js b/server/src/db/initialization.js index f2a0f2fd3..0ed865181 100644 --- a/server/src/db/initialization.js +++ b/server/src/db/initialization.js @@ -1,11 +1,11 @@ /* eslint-disable no-console */ const Knex = require('knex') -const { database: { schemas: { scanner, manual } } } = require('../configs/config.example.json') +const { database: { schemas: exampleSchemas } } = require('../configs/local.example.json') const { database: { schemas, settings }, devOptions: { queryDebug } } = require('../services/config') const models = require('../models/index') // Establishes knex connections to each database listed in the config -const connections = Object.values(schemas).map(schema => Knex({ +const connections = schemas.map(schema => Knex({ client: 'mysql2', connection: { host: schema.host, @@ -24,7 +24,7 @@ const connections = Object.values(schemas).map(schema => Knex({ })) // Binds the models to the designated databases -Object.values(schemas).forEach((schema, index) => { +schemas.forEach((schema, index) => { try { schema.useFor.forEach(category => { const capital = `${category.charAt(0).toUpperCase()}${category.slice(1)}` @@ -32,7 +32,7 @@ Object.values(schemas).forEach((schema, index) => { }) } catch (e) { console.error(` - Only ${[...scanner.useFor, ...manual.useFor].join(', ')} are valid options in the useFor fields`, '\n\n', e) + Only ${[exampleSchemas.flatMap(s => s.useFor)].join(', ')} are valid options in the useFor fields`, '\n\n', e) process.exit(9) } }) diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index 7e7658f28..7d7f352cc 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -1,15 +1,10 @@ /* eslint-disable no-console */ const GraphQLJSON = require('graphql-type-json') -const fs = require('fs') const { raw } = require('objection') const config = require('../services/config') -const scanAreas = fs.existsSync('server/src/configs/areas.json') - // eslint-disable-next-line global-require - ? require('../configs/areas.json') - : { features: [] } const { - Device, Gym, Pokemon, Pokestop, Portal, S2cell, Spawnpoint, Weather, Nest, User, + Device, Gym, Pokemon, Pokestop, Portal, ScanCell, Spawnpoint, Weather, Nest, User, } = require('../models/index') const Utility = require('../services/Utility') const Fetch = require('../services/Fetch') @@ -20,7 +15,7 @@ module.exports = { devices: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms if (perms?.devices) { - return Device.getAllDevices({ areaRestrictions: [] }, Utility.dbSelection('device') === 'mad') + return Device.getAllDevices({ areaRestrictions: [] }, Utility.dbSelection('device').type === 'mad') } return [] }, @@ -37,7 +32,7 @@ module.exports = { gyms: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms if (perms?.gyms || perms?.raids) { - return Gym.getAllGyms(args, perms, Utility.dbSelection('gym') === 'mad') + return Gym.getAllGyms(args, perms, Utility.dbSelection('gym').type === 'mad') } return [] }, @@ -46,7 +41,7 @@ module.exports = { if (perms?.[args.perm]) { const query = Gym.query() .findById(args.id) - if (Utility.dbSelection('gym') === 'mad') { + if (Utility.dbSelection('gym').type === 'mad') { query.select([ 'latitude AS lat', 'longitude AS lon', @@ -76,7 +71,7 @@ module.exports = { || perms?.lures || perms?.quests || perms?.invasions) { - return Pokestop.getAllPokestops(args, perms, Utility.dbSelection('pokestop') === 'mad') + return Pokestop.getAllPokestops(args, perms, Utility.dbSelection('pokestop').type === 'mad') } return [] }, @@ -85,7 +80,7 @@ module.exports = { if (perms?.[args.perm]) { const query = Pokestop.query() .findById(args.id) - if (Utility.dbSelection('pokestop') === 'mad') { + if (Utility.dbSelection('pokestop').type === 'mad') { query.select([ 'latitude AS lat', 'longitude AS lon', @@ -98,7 +93,7 @@ module.exports = { pokemon: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms if (perms?.pokemon) { - const isMad = Utility.dbSelection('pokemon') === 'mad' + const isMad = Utility.dbSelection('pokemon').type === 'mad' if (args.filters.onlyLegacy) { return Pokemon.getLegacy(args, perms, isMad) } @@ -110,7 +105,7 @@ module.exports = { const perms = req.user ? req.user.perms : req.session.perms if (perms?.[args.perm]) { const query = Pokemon.query().findById(args.id) - if (Utility.dbSelection('pokemon') === 'mad') { + if (Utility.dbSelection('pokemon').type === 'mad') { query.select([ 'latitude AS lat', 'longitude AS lon', @@ -134,29 +129,30 @@ module.exports = { } return {} }, - s2cells: (parent, args, { req }) => { + scanCells: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms - if (perms?.s2cells && args.zoom >= config.map.scanCellsZoom) { - return S2cell.getAllCells(args, perms, Utility.dbSelection('pokestop') === 'mad') + if (perms?.scanCells && args.zoom >= config.map.scanCellsZoom) { + return ScanCell.getAllCells(args, perms, Utility.dbSelection('pokestop').type === 'mad') } return [] }, scanAreas: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms - if (perms?.scanAreas && scanAreas.features.length) { + if (perms?.scanAreas && config.scanAreas.features.length) { try { - scanAreas.features = scanAreas.features.sort((a, b) => (a.properties.name > b.properties.name) ? 1 : -1) + config.scanAreas.features = config.scanAreas.features + .sort((a, b) => (a.properties.name > b.properties.name) ? 1 : -1) } catch (e) { console.warn('Failed to sort scan areas', e.message) } } - return [scanAreas] + return [config.scanAreas] }, search: async (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms const { category, webhookName } = args if (perms?.[category]) { - const isMad = Utility.dbSelection(category.substring(0, category.length - 1)) === 'mad' + const isMad = Utility.dbSelection(category.substring(0, category.length - 1)).type === 'mad' const distance = raw(`ROUND(( 3959 * acos( cos( radians(${args.lat}) ) * cos( radians( ${isMad ? 'latitude' : 'lat'} ) ) * cos( radians( ${isMad ? 'longitude' : 'lon'} ) - radians(${args.lon}) ) + sin( radians(${args.lat}) ) * sin( radians( ${isMad ? 'latitude' : 'lat'} ) ) ) ),2)`).as('distance') if (args.search === '') { @@ -194,7 +190,7 @@ module.exports = { const perms = req.user ? req.user.perms : req.session.perms const { category } = args if (perms?.[category]) { - const isMad = Utility.dbSelection(category.substring(0, category.length - 1)) === 'mad' + const isMad = Utility.dbSelection(category.substring(0, category.length - 1)).type === 'mad' const distance = raw(`ROUND(( 3959 * acos( cos( radians(${args.lat}) ) * cos( radians( ${isMad ? 'latitude' : 'lat'} ) ) * cos( radians( ${isMad ? 'longitude' : 'lon'} ) - radians(${args.lon}) ) + sin( radians(${args.lat}) ) * sin( radians( ${isMad ? 'latitude' : 'lat'} ) ) ) ),2)`).as('distance') if (args.search === '') { @@ -206,15 +202,15 @@ module.exports = { spawnpoints: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms if (perms?.spawnpoints) { - return Spawnpoint.getAllSpawnpoints(args, perms, Utility.dbSelection('spawnpoint') === 'mad') + return Spawnpoint.getAllSpawnpoints(args, perms, Utility.dbSelection('spawnpoint').type === 'mad') } return [] }, submissionCells: async (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms if (perms?.submissionCells && args.zoom >= config.map.submissionZoom - 1) { - const isMadStops = Utility.dbSelection('pokestop') === 'mad' - const isMadGyms = Utility.dbSelection('gym') === 'mad' + const isMadStops = Utility.dbSelection('pokestop').type === 'mad' + const isMadGyms = Utility.dbSelection('gym').type === 'mad' const stopQuery = Pokestop.query() if (isMadStops) { @@ -266,7 +262,7 @@ module.exports = { weather: (parent, args, { req }) => { const perms = req.user ? req.user.perms : req.session.perms if (perms?.weather) { - return Weather.getAllWeather(Utility.dbSelection('weather') === 'mad') + return Weather.getAllWeather(Utility.dbSelection('weather').type === 'mad') } return [] }, diff --git a/server/src/graphql/scannerTypes.js b/server/src/graphql/scannerTypes.js index 409c5f910..5663a5882 100644 --- a/server/src/graphql/scannerTypes.js +++ b/server/src/graphql/scannerTypes.js @@ -159,7 +159,7 @@ module.exports = gql` updated: Int } - type S2cell { + type ScanCell { id: ID level: Int center_lat: Float diff --git a/server/src/graphql/typeDefs.js b/server/src/graphql/typeDefs.js index 2b5334f66..123fd344d 100644 --- a/server/src/graphql/typeDefs.js +++ b/server/src/graphql/typeDefs.js @@ -23,7 +23,7 @@ module.exports = gql` pokemonSingle(id: ID, perm: String): Pokemon portals(minLat: Float, maxLat: Float, minLon: Float, maxLon: Float, ts: Int, filters: JSON): [Portal] portalsSingle(id: ID, perm: String): Portal - s2cells(minLat: Float, maxLat: Float, minLon: Float, maxLon: Float, ts: Int, filters: JSON, zoom: Int): [S2cell] + scanCells(minLat: Float, maxLat: Float, minLon: Float, maxLon: Float, ts: Int, filters: JSON, zoom: Int): [ScanCell] scanAreas: [ScanArea] search(search: String, category: String, lat: Float, lon: Float, locale: String, webhookName: String, ts: Int, midnight: Int): [Search] searchQuest(search: String, category: String, lat: Float, lon: Float, locale: String, webhookName: String, ts: Int, midnight: Int): [SearchQuest] diff --git a/server/src/index.js b/server/src/index.js index e9a92f871..dce5d7e0d 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -3,7 +3,6 @@ /* eslint-disable global-require */ const express = require('express') const path = require('path') -const fs = require('fs') const logger = require('morgan') const compression = require('compression') const session = require('express-session') @@ -14,10 +13,10 @@ const Backend = require('i18next-fs-backend') const { ApolloServer } = require('apollo-server-express') require('./db/initialization') +const config = require('./services/config') const { Pokemon } = require('./models/index') const { sessionStore } = require('./services/sessionStore') const rootRouter = require('./routes/rootRouter') -const config = require('./services/config') const typeDefs = require('./graphql/typeDefs') const resolvers = require('./graphql/resolvers') @@ -88,17 +87,13 @@ app.use(session({ cookie: { maxAge: 604800000 }, })) -fs.readdir(`${__dirname}/strategies/`, (e, files) => { - if (e) return console.error(e) - files.forEach(file => { - const trimmed = file.replace('.js', '') - if (config[trimmed]?.enabled) { - require(`./strategies/${trimmed}`) - console.log(file, 'strategy initialized') - } else { - console.log(file, 'strategy not enabled, if this was a mistake, make sure to add it to the config and enable it') - } - }) +config.authentication.strategies.forEach(strategy => { + if (strategy.enabled) { + require(`./strategies/${strategy.name}.js`) + console.log(`Strategy ${strategy.name} initialized`) + } else { + console.log(`Strategy ${strategy.name} was not initialized`) + } }) app.use(passport.initialize()) @@ -120,7 +115,7 @@ passport.deserializeUser(async (user, done) => { i18next.use(Backend).init({ lng: 'en', fallbackLng: 'en', - preload: config.localeSelection, + preload: config.map.localeSelection, ns: ['translation'], defaultNS: 'translation', backend: { loadPath: 'public/locales/{{lng}}/{{ns}}.json' }, @@ -143,7 +138,7 @@ app.use((err, req, res, next) => { } }) -if (config.database.settings.reactMapHandlesPvp) { +if (config.api.pvp.reactMapHandlesPvp) { (async () => Pokemon.initOhbem())() } diff --git a/server/src/models/Device.js b/server/src/models/Device.js index 280edffa0..855ae4d3f 100644 --- a/server/src/models/Device.js +++ b/server/src/models/Device.js @@ -2,13 +2,13 @@ const { Model, raw } = require('objection') const dbSelection = require('../services/functions/dbSelection') const getAreaSql = require('../services/functions/getAreaSql') -class Device extends Model { +module.exports = class Device extends Model { static get tableName() { - return dbSelection('device') === 'mad' ? 'settings_device' : 'device' + return dbSelection('device').type === 'mad' ? 'settings_device' : 'device' } static get idColumn() { - return dbSelection('device') === 'mad' ? 'device_id' : 'uuid' + return dbSelection('device').type === 'mad' ? 'device_id' : 'uuid' } static async getAllDevices(perms, isMad) { @@ -42,5 +42,3 @@ class Device extends Model { return query } } - -module.exports = Device diff --git a/server/src/models/Filters.js b/server/src/models/Filters.js index b987dfb3e..0d4c6e990 100644 --- a/server/src/models/Filters.js +++ b/server/src/models/Filters.js @@ -1,5 +1,5 @@ /* eslint-disable max-classes-per-file */ -const { database: { settings: { leagues } } } = require('../services/config') +const { api: { pvp: { leagues } } } = require('../services/config') class GenericFilter { constructor(enabled, size) { @@ -9,7 +9,7 @@ class GenericFilter { } class PokemonFilter extends GenericFilter { - constructor(iv, level, atk, def, sta, enabled, size) { + constructor(iv, level, atk, def, sta, pvp, enabled, size) { super(enabled, size) this.iv = iv || [0, 100] this.atk_iv = atk || [0, 15] @@ -17,10 +17,7 @@ class PokemonFilter extends GenericFilter { this.sta_iv = sta || [0, 15] this.level = level || [1, 35] this.adv = '' - } - - pvp(values) { - leagues.forEach(league => this[league.name] = values || [(league.minRank || 1), (league.maxRank || 100)]) + leagues.forEach(league => this[league.name] = pvp || [(league.minRank || 1), (league.maxRank || 100)]) } } diff --git a/server/src/models/Gym.js b/server/src/models/Gym.js index 277f0f732..3b65a9eed 100644 --- a/server/src/models/Gym.js +++ b/server/src/models/Gym.js @@ -7,13 +7,13 @@ const dbSelection = require('../services/functions/dbSelection') const getAreaSql = require('../services/functions/getAreaSql') const { api: { searchResultsLimit } } = require('../services/config') -class Gym extends Model { +module.exports = class Gym extends Model { static get tableName() { return 'gym' } static get idColumn() { - return dbSelection('gym') === 'mad' + return dbSelection('gym').type === 'mad' ? 'gym_id' : 'id' } @@ -286,5 +286,3 @@ class Gym extends Model { return query } } - -module.exports = Gym diff --git a/server/src/models/Nest.js b/server/src/models/Nest.js index afd58a168..c51c4623c 100644 --- a/server/src/models/Nest.js +++ b/server/src/models/Nest.js @@ -5,7 +5,7 @@ const getAreaSql = require('../services/functions/getAreaSql') const { pokemon: masterPkmn } = require('../data/masterfile.json') const { api: { searchResultsLimit } } = require('../services/config') -class Nest extends Model { +module.exports = class Nest extends Model { static get tableName() { return 'nests' } @@ -85,5 +85,3 @@ class Nest extends Model { return query } } - -module.exports = Nest diff --git a/server/src/models/Pokemon.js b/server/src/models/Pokemon.js index aefcef54d..2cb3814fd 100644 --- a/server/src/models/Pokemon.js +++ b/server/src/models/Pokemon.js @@ -4,15 +4,12 @@ const Ohbem = require('ohbem') const { pokemon: masterfile } = require('../data/masterfile.json') const legacyFilter = require('../services/legacyFilter') const { - api: { pvpMinCp }, - database: { - settings: { leagues, reactMapHandlesPvp, pvpLevels }, - }, + api: { pvp: { minCp: pvpMinCp, leagues, reactMapHandlesPvp, levels } }, } = require('../services/config') const dbSelection = require('../services/functions/dbSelection') const getAreaSql = require('../services/functions/getAreaSql') -const dbType = dbSelection('pokemon') +const { type: dbType } = dbSelection('pokemon') const levelCalc = 'IFNULL(IF(cp_multiplier < 0.734, ROUND(58.35178527 * cp_multiplier * cp_multiplier - 2.838007664 * cp_multiplier + 0.8539209906), ROUND(171.0112688 * cp_multiplier - 95.20425243)), NULL)' const ivCalc = 'IFNULL((individual_attack + individual_defense + individual_stamina) / 0.45, NULL)' const keys = ['iv', 'level', 'atk_iv', 'def_iv', 'sta_iv', ...leagues.map(league => league.name)] @@ -58,13 +55,13 @@ const getMadSql = q => ( ]) ) -class Pokemon extends Model { +module.exports = class Pokemon extends Model { static get tableName() { return 'pokemon' } static get idColumn() { - return dbSelection('pokemon') === 'mad' + return dbSelection('pokemon').type === 'mad' ? 'encounter_id' : 'id' } @@ -77,14 +74,14 @@ class Pokemon extends Model { ohbem = new Ohbem({ leagues: leagueObj, pokemonData: await Ohbem.fetchPokemonData(), - levelCaps: pvpLevels, + levelCaps: levels, cachingStrategy: Ohbem.cachingStrategies.memoryHeavy, }) } static async getPokemon(args, perms, isMad) { const { - stats, iv: ivs, pvp, areaRestrictions, + iv: ivs, pvp, areaRestrictions, } = perms const { onlyStandard, onlyIvOr, onlyXlKarp, onlyXsRat, onlyZeroIv, onlyHundoIv, onlyPvpMega, onlyLinkGlobal, ts, @@ -93,7 +90,7 @@ class Pokemon extends Model { const safeTs = ts || Math.floor((new Date()).getTime() / 1000) // quick check to make sure no Pokemon are returned when none are enabled for users with only Pokemon perms - if (!ivs && !stats && !pvp) { + if (!ivs && !pvp) { const noPokemonSelect = Object.keys(args.filters).find(x => x.charAt(0) !== 'o') if (!noPokemonSelect) return [] } @@ -184,15 +181,12 @@ class Pokemon extends Model { queryBase.whereNull('pokemon_id') } } break - case 'iv': - if (ivs) { - queryBase.andWhereBetween(isMad ? madKeys[key] : key, filter[key]) - } break case 'level': case 'atk_iv': case 'def_iv': case 'sta_iv': - if (stats) { + case 'iv': + if (ivs) { queryBase.andWhereBetween(isMad ? madKeys[key] : key, filter[key]) } break } @@ -236,7 +230,7 @@ class Pokemon extends Model { generateSql(poke, filter, relevantFilters, true) } }) - } else if (pkmn === 'onlyIvOr' && (ivs || stats || pvp)) { + } else if (pkmn === 'onlyIvOr' && (ivs || pvp)) { const relevantFilters = getRelevantKeys(filter) if (relevantFilters.length > 0) { generateSql(ivOr, filter, relevantFilters) @@ -397,5 +391,3 @@ class Pokemon extends Model { return results.map(pkmn => `${pkmn.pokemon_id}-${pkmn.form}`) } } - -module.exports = Pokemon diff --git a/server/src/models/Pokestop.js b/server/src/models/Pokestop.js index e67be82a5..3274bf020 100644 --- a/server/src/models/Pokestop.js +++ b/server/src/models/Pokestop.js @@ -5,7 +5,11 @@ const { pokemon: masterPkmn, items: masterItems, questRewardTypes } = require('. const fetchQuests = require('../services/api/fetchQuests') const dbSelection = require('../services/functions/dbSelection') const getAreaSql = require('../services/functions/getAreaSql') -const { api: { searchResultsLimit }, database: { schemas, settings }, map } = require('../services/config') +const { + api: { searchResultsLimit }, + database: { settings }, + map, +} = require('../services/config') const questProps = { quest_type: true, @@ -36,11 +40,10 @@ const invasionProps = { incident_expire_timestamp: true, grunt_type: true, } -const dbType = dbSelection('pokestop') -const { hasAltQuests } = Object.values(schemas).find(schema => schema.useFor.includes('pokestop')) -const altQuestCheck = hasAltQuests && dbType !== 'mad' +const { type, hasAltQuests } = dbSelection('pokestop') +const altQuestCheck = hasAltQuests && type !== 'mad' -class Pokestop extends Model { +module.exports = class Pokestop extends Model { static get tableName() { return 'pokestop' } @@ -90,7 +93,7 @@ class Pokestop extends Model { .as('incident_expire_timestamp'), ]) } - if (dbType === 'chuck') { + if (type === 'chuck') { query.join('incident', 'pokestop.id', 'incident.pokestop_id') .select([ '*', @@ -226,7 +229,7 @@ class Pokestop extends Model { }) } if (onlyInvasions && invasionPerms) { - if (dbType === 'chuck') { + if (type === 'chuck') { stops.orWhere(invasion => { invasion.whereIn('character', invasions) .andWhere('expiration_ms', '>=', safeTs * 1000) @@ -506,8 +509,8 @@ class Pokestop extends Model { } } - Object.entries(quests).forEach(([type, rewards]) => { - switch (type) { + Object.entries(quests).forEach(([questType, rewards]) => { + switch (questType) { case 'items': rewards.forEach(reward => finalList.add(`q${reward.quest_item_id}`)); break case 'mega': rewards.forEach(reward => finalList.add(`m${reward.id}-${reward.amount}`)); break case 'invasions': rewards.forEach(reward => finalList.add(`i${reward.grunt_type}`)); break @@ -534,8 +537,8 @@ class Pokestop extends Model { .orderBy(isMad ? 'active_fort_modifier' : 'lure_id') Object.entries(stops).forEach(stopType => { - const [type, rewards] = stopType - switch (type) { + const [sType, rewards] = stopType + switch (sType) { default: rewards.forEach(reward => finalList.add(`i${reward.grunt_type}`)); break case 'lures': rewards.forEach(reward => finalList.add(`l${reward.lure_id}`)); break } @@ -605,8 +608,8 @@ class Pokestop extends Model { const itemIds = Object.keys(masterItems).filter(item => ( i18next.t(`item_${item}`, { lng: locale }).toLowerCase().includes(search) )) - const rewardTypes = Object.keys(questRewardTypes).filter(type => ( - i18next.t(`quest_reward_${type}`, { lng: locale }).toLowerCase().includes(search) + const rewardTypes = Object.keys(questRewardTypes).filter(rType => ( + i18next.t(`quest_reward_${rType}`, { lng: locale }).toLowerCase().includes(search) )) const query = this.query() @@ -679,5 +682,3 @@ class Pokestop extends Model { return results.map(result => isMad ? this.parseMadRewards(result) : this.parseRdmRewards(result)).filter(x => x) } } - -module.exports = Pokestop diff --git a/server/src/models/Portal.js b/server/src/models/Portal.js index 9f61ee2c2..279fffe78 100644 --- a/server/src/models/Portal.js +++ b/server/src/models/Portal.js @@ -1,8 +1,10 @@ const { Model } = require('objection') const getAreaSql = require('../services/functions/getAreaSql') -const { api: { searchResultsLimit, portalUpdateLimit } } = require('../services/config') +const { + api: { searchResultsLimit, portalUpdateLimit }, +} = require('../services/config') -class Portal extends Model { +module.exports = class Portal extends Model { static get tableName() { return 'ingress_portals' } @@ -40,5 +42,3 @@ class Portal extends Model { return query } } - -module.exports = Portal diff --git a/server/src/models/Ring.js b/server/src/models/Ring.js index ee41c6511..2ec31c31c 100644 --- a/server/src/models/Ring.js +++ b/server/src/models/Ring.js @@ -1,9 +1,7 @@ -class Ring { +module.exports = class Ring { constructor(id, lat, lon) { this.id = id this.lat = lat this.lon = lon } } - -module.exports = Ring diff --git a/server/src/models/S2cell.js b/server/src/models/ScanCell.js similarity index 90% rename from server/src/models/S2cell.js rename to server/src/models/ScanCell.js index ab33baef1..7315d6503 100644 --- a/server/src/models/S2cell.js +++ b/server/src/models/ScanCell.js @@ -3,9 +3,9 @@ const dbSelection = require('../services/functions/dbSelection') const getPolyVector = require('../services/functions/getPolyVector') const getAreaSql = require('../services/functions/getAreaSql') -class S2cell extends Model { +module.exports = class ScanCell extends Model { static get tableName() { - return dbSelection('s2cell') === 'mad' + return dbSelection('scanCell').type === 'mad' ? 'trs_s2cells' : 's2cell' } @@ -27,5 +27,3 @@ class S2cell extends Model { })) } } - -module.exports = S2cell diff --git a/server/src/models/Session.js b/server/src/models/Session.js index 7884186ef..0af86b7a9 100644 --- a/server/src/models/Session.js +++ b/server/src/models/Session.js @@ -1,10 +1,8 @@ const { Model } = require('objection') const { database: { settings: { sessionTableName } } } = require('../services/config') -class Session extends Model { +module.exports = class Session extends Model { static get tableName() { return sessionTableName } } - -module.exports = Session diff --git a/server/src/models/Spawnpoint.js b/server/src/models/Spawnpoint.js index 350f93509..adc68b56c 100644 --- a/server/src/models/Spawnpoint.js +++ b/server/src/models/Spawnpoint.js @@ -2,14 +2,14 @@ const { Model, raw } = require('objection') const dbSelection = require('../services/functions/dbSelection') const getAreaSql = require('../services/functions/getAreaSql') -class Spawnpoint extends Model { +module.exports = class Spawnpoint extends Model { static get tableName() { - return dbSelection('spawnpoint') === 'mad' + return dbSelection('spawnpoint').type === 'mad' ? 'trs_spawn' : 'spawnpoint' } static get idColumn() { - return dbSelection('spawnpoint') === 'mad' + return dbSelection('spawnpoint').type === 'mad' ? 'spawnpoint' : 'id' } @@ -35,5 +35,3 @@ class Spawnpoint extends Model { return query } } - -module.exports = Spawnpoint diff --git a/server/src/models/User.js b/server/src/models/User.js index 2bde1dcc6..5efb81d89 100644 --- a/server/src/models/User.js +++ b/server/src/models/User.js @@ -2,7 +2,7 @@ const { Model } = require('objection') const { database: { settings: { userTableName } } } = require('../services/config') -class User extends Model { +module.exports = class User extends Model { static get tableName() { return userTableName } @@ -14,5 +14,3 @@ class User extends Model { .then(() => console.log(`[${botName}] Cleared ${strategy} perms for user ${userId}`)) } } - -module.exports = User diff --git a/server/src/models/Weather.js b/server/src/models/Weather.js index ccc728a73..ad1f57ba4 100644 --- a/server/src/models/Weather.js +++ b/server/src/models/Weather.js @@ -3,13 +3,13 @@ const getPolyVector = require('../services/functions/getPolyVector') const dbSelection = require('../services/functions/dbSelection') const { api: { weatherCellLimit } } = require('../services/config') -class Weather extends Model { +module.exports = class Weather extends Model { static get tableName() { return 'weather' } static get idColumn() { - return dbSelection('weather') === 'mad' + return dbSelection('weather').type === 'mad' ? 's2_cell_id' : 'id' } @@ -39,5 +39,3 @@ class Weather extends Model { })) } } - -module.exports = Weather diff --git a/server/src/models/index.js b/server/src/models/index.js index bd0a43c73..4e59eb07f 100644 --- a/server/src/models/index.js +++ b/server/src/models/index.js @@ -5,7 +5,7 @@ const Pokestop = require('./Pokestop') const Pokemon = require('./Pokemon') const Portal = require('./Portal') const Ring = require('./Ring') -const S2cell = require('./S2cell') +const ScanCell = require('./ScanCell') const Session = require('./Session') const Spawnpoint = require('./Spawnpoint') const User = require('./User') @@ -20,7 +20,7 @@ module.exports = { Pokemon, Portal, Ring, - S2cell, + ScanCell, Session, Spawnpoint, User, diff --git a/server/src/routes/authRouter.js b/server/src/routes/authRouter.js index e7bcb4935..49b180a20 100644 --- a/server/src/routes/authRouter.js +++ b/server/src/routes/authRouter.js @@ -1,24 +1,22 @@ /* eslint-disable global-require */ /* eslint-disable import/no-dynamic-require */ /* eslint-disable no-console */ -const fs = require('fs') const router = require('express').Router() const passport = require('passport') const { isValidSession, clearOtherSessions } = require('../services/sessionStore') +const { authentication: { strategies } } = require('../services/config') // Loads up the base auth routes and any custom ones -fs.readdir(`${__dirname}/../strategies/`, (e, files) => { - if (e) return console.error(e) - files.forEach((file) => { - const trimmed = file.replace('.js', '') - const method = trimmed.includes('local') ? 'post' : 'get' - router[method](`/${trimmed}`, passport.authenticate(trimmed, { +strategies.forEach(strategy => { + const method = strategy.type === 'local' ? 'post' : 'get' + if (strategy.enabled) { + router[method](`/${strategy.name}`, passport.authenticate(strategy.name, { successRedirect: '/', failureMessage: true, })) - router[method](`/${trimmed}/callback`, - async (req, res, next) => passport.authenticate(trimmed, async (err, user, info) => { + router[method](`/${strategy.name}/callback`, + async (req, res, next) => passport.authenticate(strategy.name, async (err, user, info) => { if (err) { return next(err) } if (!user) { res.status(401).json(info.message) @@ -38,8 +36,8 @@ fs.readdir(`${__dirname}/../strategies/`, (e, files) => { } } })(req, res, next)) - console.log(`${method.toUpperCase()} /auth/${trimmed}/callback route initialized`) - }) + console.log(`${method.toUpperCase()} /auth/${strategy.name}/callback route initialized`) + } }) module.exports = router diff --git a/server/src/routes/clientRouter.js b/server/src/routes/clientRouter.js index f0d86a6d7..ee40637f5 100644 --- a/server/src/routes/clientRouter.js +++ b/server/src/routes/clientRouter.js @@ -1,6 +1,6 @@ const express = require('express') const path = require('path') -const config = require('../services/config') +const { devOptions } = require('../services/config') const router = new express.Router() @@ -15,7 +15,7 @@ const clientRoutes = [ ] router.get(clientRoutes, (req, res) => { - res.sendFile(path.join(__dirname, `../${config.devOptions.clientPath}/index.html`)) + res.sendFile(path.join(__dirname, `../${devOptions.clientPath}/index.html`)) }) module.exports = router diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index d20bd5c06..33321df0a 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -1,6 +1,5 @@ /* eslint-disable no-console */ const express = require('express') -const fs = require('fs') const { default: center } = require('@turf/center') const authRouter = require('./authRouter') @@ -30,18 +29,18 @@ rootRouter.get('/logout', (req, res) => { rootRouter.get('/area/:area/:zoom?', (req, res) => { const { area, zoom } = req.params try { - const scanAreas = fs.existsSync('server/src/configs/areas.json') - // eslint-disable-next-line global-require - ? require('../configs/areas.json') - : { features: [] } + const { scanAreas, manualAreas } = config if (scanAreas.features.length) { const foundArea = scanAreas.features.find(a => a.properties.name.toLowerCase() === area.toLowerCase()) if (foundArea) { const [lon, lat] = center(foundArea).geometry.coordinates - res.redirect(`/@/${lat}/${lon}/${zoom || 15}`) - } else { - res.redirect('/404') + return res.redirect(`/@/${lat}/${lon}/${zoom || 18}`) } + if (manualAreas.length) { + const { lat, lon } = manualAreas.find(a => a.name.toLowerCase() === area.toLowerCase()) + return res.redirect(`/@/${lat}/${lon}/${zoom || 18}`) + } + return res.redirect('/404') } } catch (e) { console.error(`Error navigating to ${area}`, e.message) @@ -51,13 +50,13 @@ rootRouter.get('/area/:area/:zoom?', (req, res) => { rootRouter.get('/settings', async (req, res) => { try { - if (!config.authMethods.length || config.alwaysEnabledPerms.length) { + if (!config.authMethods.length || config.authentication.alwaysEnabledPerms.length) { req.session.perms = { areaRestrictions: [], webhooks: [] } req.session.save() } - if (config.alwaysEnabledPerms.length) { - config.alwaysEnabledPerms.forEach(perm => { - if (config.discord.perms[perm]) { + if (config.authentication.alwaysEnabledPerms.length) { + config.authentication.alwaysEnabledPerms.forEach(perm => { + if (config.authentication.perms[perm]) { req.session.perms[perm] = true } else { console.warn('Invalid Perm in "alwaysEnabledPerms" array:', perm) @@ -94,12 +93,12 @@ rootRouter.get('/settings', async (req, res) => { config: { map: { ...config.map, - ...config.multiDomains[req.headers.host], - excludeList: config.excludeFromTutorial, + ...config.multiDomainsObj[req.headers.host], + excludeList: config.authentication.excludeFromTutorial, }, - localeSelection: Object.fromEntries(config.localeSelection.map(locale => [locale, { name: locale }])), - tileServers: { auto: {}, ...config.tileServers }, - navigation: config.navigation, + localeSelection: Object.fromEntries(config.map.localeSelection.map(l => [l, { name: l }])), + tileServers: Object.fromEntries(config.tileServers.map(s => [s.name, s])), + navigation: Object.fromEntries(config.navigation.map(n => [n.name, n])), drawer: { temporary: {}, persistent: {}, @@ -111,6 +110,12 @@ rootRouter.get('/settings', async (req, res) => { manualAreas: config.manualAreas || {}, icons: config.icons, }, + available: { + pokemon: [], + pokestops: [], + gyms: [], + nests: [], + }, } // add user options here from the config that are structured as objects @@ -121,32 +126,29 @@ rootRouter.get('/settings', async (req, res) => { const ignoreKeys = ['map', 'manualAreas', 'limit', 'icons'] Object.keys(serverSettings.config).forEach(setting => { - if (!ignoreKeys.includes(setting)) { - const category = serverSettings.config[setting] - Object.keys(category).forEach(option => { - category[option].name = option - }) - if (config.map[setting] && typeof config.map[setting] !== 'object') { - serverSettings.settings[setting] = config.map[setting] - } else { - serverSettings.settings[setting] = category[Object.keys(category)[0]].name + try { + if (!ignoreKeys.includes(setting)) { + const category = serverSettings.config[setting] + Object.keys(category).forEach(option => { + category[option].name = option + }) + if (config.map[setting] && typeof config.map[setting] !== 'object') { + serverSettings.settings[setting] = config.map[setting] + } else { + serverSettings.settings[setting] = category[Object.keys(category)[0]].name + } } + } catch (e) { + console.warn(`Error setting ${setting}, most likely means there are no options set in the config`, e.message) } }) serverSettings.defaultFilters = Utility.buildDefaultFilters(serverSettings.user.perms) - serverSettings.available = { - pokemon: [], - pokestops: [], - gyms: [], - nests: [], - } - try { if (serverSettings.user.perms.pokemon) { serverSettings.available.pokemon = config.api.queryAvailable.pokemon - ? await Pokemon.getAvailablePokemon(Utility.dbSelection('pokemon') === 'mad') + ? await Pokemon.getAvailablePokemon(Utility.dbSelection('pokemon').type === 'mad') : [] } } catch (e) { @@ -155,7 +157,7 @@ rootRouter.get('/settings', async (req, res) => { try { if (serverSettings.user.perms.raids || serverSettings.user.perms.gyms) { serverSettings.available.gyms = config.api.queryAvailable.raids - ? await Gym.getAvailableRaidBosses(Utility.dbSelection('gym') === 'mad') + ? await Gym.getAvailableRaidBosses(Utility.dbSelection('gym').type === 'mad') : await Fetch.fetchRaids() } } catch (e) { @@ -167,7 +169,7 @@ rootRouter.get('/settings', async (req, res) => { || serverSettings.user.perms.invasions || serverSettings.user.perms.lures) { serverSettings.available.pokestops = config.api.queryAvailable.quests - ? await Pokestop.getAvailableQuests(Utility.dbSelection('pokestop') === 'mad') + ? await Pokestop.getAvailableQuests(Utility.dbSelection('pokestop').type === 'mad') : await Fetch.fetchQuests() } } catch (e) { @@ -252,7 +254,7 @@ rootRouter.get('/settings', async (req, res) => { .sort((a, b) => a.name.localeCompare(b.name)) .filter(area => area.userSelectable !== false) .map(area => area.name), - templates: config.webhookObj[webhook.name].client.templates[strategy], + templates: config.webhookObj[webhook.name].client.templates[webhookStrategy || strategy], } } } diff --git a/server/src/services/DiscordClient.js b/server/src/services/DiscordClient.js index 052612260..41929697e 100644 --- a/server/src/services/DiscordClient.js +++ b/server/src/services/DiscordClient.js @@ -6,7 +6,7 @@ /* eslint-disable import/no-dynamic-require */ /* global BigInt */ const fs = require('fs') -const { alwaysEnabledPerms, webhooks } = require('./config') +const { authentication: { alwaysEnabledPerms }, webhooks } = require('./config') const Utility = require('./Utility') module.exports = class DiscordMapClient { diff --git a/server/src/services/api/fetchNests.js b/server/src/services/api/fetchNests.js index a78e87ca8..da481cff3 100644 --- a/server/src/services/api/fetchNests.js +++ b/server/src/services/api/fetchNests.js @@ -1,5 +1,5 @@ const fetchJson = require('./fetchJson') -const { map: { nestHemisphere } } = require('../config') +const { api: { nestHemisphere } } = require('../config') const { pokemon: masterfile } = require('../../data/masterfile.json') module.exports = async function fetchNests() { diff --git a/server/src/services/api/resolveQuickHook.js b/server/src/services/api/resolveQuickHook.js index afad5a1ca..0c388ec96 100644 --- a/server/src/services/api/resolveQuickHook.js +++ b/server/src/services/api/resolveQuickHook.js @@ -2,14 +2,6 @@ const fetchJson = require('./fetchJson') const config = require('../config') -// const looper = (num, toLoop, staticData, skipZero) => { -// const arr = [] -// for (let i = skipZero ? 1 : 0; i <= num; i += 1) { -// arr.push({ ...staticData, [toLoop]: i }) -// } -// return arr -// } - const getWildCards = (category) => { switch (category) { case 'gym': return { team: 4, slot_changes: true, battle_changes: true } diff --git a/server/src/services/areas.js b/server/src/services/areas.js index 286c9ac74..58e76e6bc 100644 --- a/server/src/services/areas.js +++ b/server/src/services/areas.js @@ -1,20 +1,15 @@ +/* eslint-disable no-console */ /* eslint-disable no-restricted-syntax */ -const fs = require('fs') -const path = require('path') -const { - discord: { areaRestrictions: discord }, - telegram: { areaRestrictions: telegram }, -} = require('./config') +const config = require('./config') const loadAreas = () => { let areas = {} - const areasFilePath = path.resolve(__dirname, '../configs/areas.json') try { // eslint-disable-next-line global-require - const data = fs.existsSync(areasFilePath, 'utf8') ? require('../configs/areas.json') : Error('Areas file not found') + const data = config.scanAreas || Error('Areas file not found') areas = data } catch (err) { - const showWarning = discord.some(rule => rule.roles.length) || telegram.some(rule => rule.roles.length) + const showWarning = config.authentication.areaRestrictions.some(rule => rule.roles.length) if (showWarning) { console.warn('[Area Restrictions] Disabled - `areas.json` file is missing or broken.') } diff --git a/server/src/services/config.js b/server/src/services/config.js index 0663a9b05..2ba7de429 100644 --- a/server/src/services/config.js +++ b/server/src/services/config.js @@ -1,58 +1,61 @@ +/* eslint-disable global-require */ /* eslint-disable no-console */ -const extend = require('extend') +process.env.NODE_CONFIG_DIR = `${__dirname}/../configs` + const fs = require('fs') -const uConfig = require('../configs/config.json') -const eConfig = require('../configs/default.json') -const initWebhooks = require('./initWebhooks') +const config = require('config') -const target = {} - -extend(true, target, eConfig, uConfig) - -try { - target.authMethods = [] - fs.readdir(`${__dirname}/../strategies/`, (e, files) => { - if (e) return console.error(e) - files.forEach(file => { - const trimmed = file.replace('.js', '') - if (target[trimmed]?.enabled) { - target.authMethods.push(trimmed) - } - }) - }) -} catch (e) { - console.error('Failed to initialize a strategy', e) +if (!fs.existsSync(`${__dirname}/../configs/local.json`)) { + console.log('Config v2 (local.json) not found, you need to run the migration with "yarn config-migrate"') + process.exit(1) } -if (target.map.messageOfTheDay.messages) { - console.warn('You are using an old API endpoint in the message of the day section, the [messages] array should be renamed to [components]! \n They have been migrated for you in the meantime but you should update your config.json file.') - - const updateFieldRec = (messages) => messages.map(message => { - if (message.messages) { - message.components = updateFieldRec(message.messages) - delete message.messages - } - return message - }) - - target.map.messageOfTheDay.components = target.map.messageOfTheDay.messages.map(m => { - if (m.type !== 'parent') return m - if (m.messages) { - m.components = m.components || updateFieldRec(m.messages) - delete m.messages - } - return m - }) - delete target.map.messageOfTheDay.messages -} +const initWebhooks = require('./initWebhooks') -if (target.icons.defaultIcons.misc) { - console.warn('Warning: Setting the misc category to anything does not have an impact on the icons.') -} -if (target.webhooks.length) { +const mergeMapConfig = (obj) => ({ + localeSelection: obj.localeSelection, + ...obj, + ...obj.general, + ...obj.customRoutes, + ...obj.links, + ...obj.holidayEffects, + ...obj.misc, + general: undefined, + customRoutes: undefined, + links: undefined, + holidayEffects: undefined, + misc: undefined, +}) + +config.map = mergeMapConfig(config.map) + +config.multiDomainsObj = Object.fromEntries( + config.multiDomains.map(d => [d.domain, mergeMapConfig(d)]), +) + +config.authMethods = [] +config.authentication.strategies.forEach(strategy => { + if (strategy.enabled) { + config.authentication[strategy.name] = strategy + config.authMethods.push(strategy.name) + } +}) + +config.map.noScanAreaOverlay = Boolean(config.manualAreas.length) + +if (config.webhooks.length) { (async () => { - target.webhookObj = await initWebhooks(target) + config.webhookObj = await initWebhooks(config) })() } +['tileServers', 'navigation'].forEach(opt => { + if (!config[opt].length) console.warn(`[${opt}] is empty, you need to add options to it or remove the empty array from your config.`) +}) + +config.scanAreas = fs.existsSync(`${__dirname}/../configs/areas.json`) + ? require('../configs/areas.json') + : { features: [] } + +config.manualAreas = Object.fromEntries(config.manualAreas.map(area => [area.name, area])) -module.exports = target +module.exports = config diff --git a/server/src/services/defaultFilters/buildDefaultFilters.js b/server/src/services/defaultFilters/buildDefaultFilters.js index 97c146c0f..35d7af63a 100644 --- a/server/src/services/defaultFilters/buildDefaultFilters.js +++ b/server/src/services/defaultFilters/buildDefaultFilters.js @@ -1,21 +1,19 @@ -const { defaultFilters, database: { schemas }, map: { legacyPkmnFilter } } = require('../config') +const { + defaultFilters, + map: { enableMapJsFilter }, +} = require('../config') const buildPokemon = require('./buildPokemon') const buildPokestops = require('./buildPokestops') const buildGyms = require('./buildGyms') const { GenericFilter, PokemonFilter } = require('../../models/index') const base = new PokemonFilter() -base.pvp() const custom = new PokemonFilter(...Object.values(defaultFilters.pokemon.globalValues)) -custom.pvp(defaultFilters.pokemon.pvpValues) module.exports = function buildDefault(perms) { const stopReducer = perms.pokestops || perms.lures || perms.quests || perms.invasions const gymReducer = perms.gyms || perms.raids - const pokemonReducer = perms.iv || perms.stats || perms.pvp - const hasAr = poi => Object.values(schemas).some( - schema => schema.useFor.includes(poi) && schema.arScanColumn === true, - ) + const pokemonReducer = perms.iv || perms.pvp const pokemon = buildPokemon(defaultFilters, base, custom) return { @@ -25,7 +23,7 @@ module.exports = function buildDefault(perms) { raids: perms.raids ? defaultFilters.gyms.raids : undefined, exEligible: perms.gyms ? defaultFilters.gyms.exEligible : undefined, inBattle: perms.gyms ? defaultFilters.gyms.exEligible : undefined, - arEligible: hasAr('gym') && perms.gyms ? false : undefined, + arEligible: perms.gyms ? false : undefined, filter: { ...buildGyms(perms, defaultFilters.gyms), ...pokemon.raids, @@ -44,7 +42,7 @@ module.exports = function buildDefault(perms) { quests: perms.quests ? defaultFilters.pokestops.quests : undefined, showQuestSet: defaultFilters.pokestops.questSet, invasions: perms.invasions ? defaultFilters.pokestops.invasions : undefined, - arEligible: hasAr('pokestop') && perms.pokestops ? false : undefined, + arEligible: perms.pokestops ? false : undefined, filter: { ...buildPokestops(perms, defaultFilters.pokestops), ...pokemon.quests, @@ -52,9 +50,8 @@ module.exports = function buildDefault(perms) { } : undefined, pokemon: perms.pokemon ? { enabled: defaultFilters.pokemon.enabled, - legacy: (pokemonReducer && legacyPkmnFilter) ? defaultFilters.pokemon.legacyFilter : undefined, + legacy: (pokemonReducer && enableMapJsFilter) ? defaultFilters.pokemon.legacyFilter : undefined, iv: perms.iv ? true : undefined, - stats: perms.stats ? true : undefined, pvp: perms.pvp ? true : undefined, standard: base, ivOr: custom, @@ -92,7 +89,7 @@ module.exports = function buildDefault(perms) { unconfirmed: new GenericFilter(), }, } : undefined, - s2cells: perms.s2cells ? { + scanCells: perms.scanCells ? { enabled: defaultFilters.scanCells.enabled, filter: { global: new GenericFilter() }, } : undefined, diff --git a/server/src/services/functions/areaPerms.js b/server/src/services/functions/areaPerms.js index 4d4499d2f..58dc17e4d 100644 --- a/server/src/services/functions/areaPerms.js +++ b/server/src/services/functions/areaPerms.js @@ -1,11 +1,11 @@ const areas = require('../areas') const config = require('../config') -module.exports = function areaPerms(roles, provider) { +module.exports = function areaPerms(roles) { let perms = [] if (Object.keys(areas.names).length) { roles.forEach(group => { - config[provider].areaRestrictions.forEach(rule => { + config.authentication.areaRestrictions.forEach(rule => { if (rule.roles.includes(group)) { if (rule.areas.length) { rule.areas.forEach(areaName => { diff --git a/server/src/services/functions/dbSelection.js b/server/src/services/functions/dbSelection.js index 75719393e..bd9106f6a 100644 --- a/server/src/services/functions/dbSelection.js +++ b/server/src/services/functions/dbSelection.js @@ -3,6 +3,5 @@ const { database: { schemas } } = require('../config') module.exports = function dbSelection(category) { if (category === 'quest' || category === 'invasion' || category === 'lure') category = 'pokestop' if (category === 'raid') category = 'gym' - const db = Object.values(schemas).find(({ useFor }) => useFor.includes(category)) - return db.type + return schemas.find(({ useFor }) => useFor.includes(category)) } diff --git a/server/src/services/functions/permissions.js b/server/src/services/functions/permissions.js index 457d58d63..d93cdfcdf 100644 --- a/server/src/services/functions/permissions.js +++ b/server/src/services/functions/permissions.js @@ -16,8 +16,6 @@ module.exports = function permissionManager(permToCheck, perms) { case 'monsters': case 'pokemon': case 'pokemons': return perms.pokemon - case 'stat': - case 'stats': return perms.stats case 'iv': case 'ivs': return perms.iv case 'pvp': diff --git a/server/src/services/legacyFilter.js b/server/src/services/legacyFilter.js index 6e99274fa..e78919f4e 100644 --- a/server/src/services/legacyFilter.js +++ b/server/src/services/legacyFilter.js @@ -6,10 +6,7 @@ const requireFromString = require('require-from-string') const masterfile = require('../data/masterfile.json') const { - api: { pvpMinCp }, - database: { - settings: { reactMapHandlesPvp }, - }, + api: { pvp: { minCp: pvpMinCp, reactMapHandlesPvp } }, } = require('./config') const jsifyIvFilter = (filter) => { @@ -251,7 +248,7 @@ const getLegacy = (results, args, perms, ohbem) => { result.seen_type = 'encounter' } } - if (perms.iv || perms.stats) { + if (perms.iv) { filtered.atk_iv = result.atk_iv filtered.def_iv = result.def_iv filtered.sta_iv = result.sta_iv @@ -324,7 +321,7 @@ const getLegacy = (results, args, perms, ohbem) => { filtered.cellId = result.cell_id filtered.expire_timestamp_verified = result.expire_timestamp_verified filtered.display_pokemon_id = result.display_pokemon_id - if (perms.iv || perms.stats) { + if (perms.iv) { filtered.move_1 = result.move_1 filtered.move_2 = result.move_2 filtered.weight = result.weight diff --git a/server/src/services/logUserAuth.js b/server/src/services/logUserAuth.js index 4cf593a3c..2244a6b8f 100644 --- a/server/src/services/logUserAuth.js +++ b/server/src/services/logUserAuth.js @@ -1,14 +1,12 @@ /* eslint-disable no-console */ const Fetch = require('./Fetch') -const config = require('./config') module.exports = async function getAuthInfo(req, user, strategy) { const ip = req.headers['cf-connecting-ip'] || ((req.headers['x-forwarded-for'] || '').split(', ')[0]) || (req.connection.remoteAddress || req.connection.localAddress).match('[0-9]+.[0-9].+[0-9]+.[0-9]+$')[0] - const url = `http://ip-api.com/json/${ip}?fields=66846719&lang=${config.map.locale || 'en'}` - const geo = await Fetch.fetchJson(url) + const geo = await Fetch.fetchJson(`http://ip-api.com/json/${ip}?fields=66846719&lang=en`) const embed = { color: 0xFF0000, title: 'Authentication', diff --git a/server/src/services/sessionStore.js b/server/src/services/sessionStore.js index 4331c44a5..cc01e7443 100644 --- a/server/src/services/sessionStore.js +++ b/server/src/services/sessionStore.js @@ -2,25 +2,27 @@ const session = require('express-session') const MySQLStore = require('express-mysql-session')(session) -const config = require('./config') - -const { database: { schemas } } = config - -const dbSelection = Object.keys(schemas).find(name => schemas[name].useFor.includes('session')) || 'scanner' +const { + api: { maxSessions }, + database: { settings: { sessionTableName } }, +} = require('./config') +const Utility = require('./Utility') const { Session } = require('../models/index') +const dbSelection = Utility.dbSelection('session') + // MySQL session store const sessionStore = new MySQLStore({ // Database server IP address/hostname - host: schemas[dbSelection].host, + host: dbSelection.host, // Database server listening port - port: schemas[dbSelection].port, + port: dbSelection.port, // Database username - user: schemas[dbSelection].username, + user: dbSelection.username, // Password for the above database user - password: schemas[dbSelection].password, + password: dbSelection.password, // Database name to save sessions table to - database: schemas[dbSelection].database, + database: dbSelection.database, // Whether or not to automatically check for and clear expired sessions: clearExpired: true, // How frequently expired sessions will be cleared; milliseconds: @@ -29,7 +31,7 @@ const sessionStore = new MySQLStore({ createDatabaseTable: true, // Set Sessions table name schema: { - tableName: config.database.settings.sessionTableName, + tableName: sessionTableName, }, }) @@ -39,7 +41,7 @@ const isValidSession = async (userId) => { .select('session_id') .whereRaw(`json_extract(data, '$.passport.user.id') = ${userId}`) .andWhere('expires', '>=', ts) - return results.length < config.api.maxSessions + return results.length < maxSessions } const clearOtherSessions = async (userId, currentSessionId, botName) => { diff --git a/server/src/services/ui/clientOptions.js b/server/src/services/ui/clientOptions.js index cd5756d70..4cfe8a9ac 100644 --- a/server/src/services/ui/clientOptions.js +++ b/server/src/services/ui/clientOptions.js @@ -1,4 +1,8 @@ -const { clientSideOptions, map: { legacyPkmnFilter }, database: { settings: { pvpLevels } } } = require('../config') +const { + clientSideOptions, + map: { enableMapJsFilter }, + api: { pvp: { levels } }, +} = require('../config') const dbSelection = require('../functions/dbSelection') module.exports = function clientOptions(perms) { @@ -42,20 +46,20 @@ module.exports = function clientOptions(perms) { }, } - pvpLevels.forEach(level => { + levels.forEach(level => { clientMenus.pokemon[`pvp${level}`] = { type: 'bool', perm: ['pvp'], value: true, } }) // special case options that require additional checks - if (legacyPkmnFilter) { - clientMenus.pokemon.legacyFilter = { type: 'bool', perm: ['iv', 'stats', 'pvp'] } + if (enableMapJsFilter) { + clientMenus.pokemon.legacyFilter = { type: 'bool', perm: ['iv', 'pvp'] } } if (clientSideOptions.pokemon.glow.length > 0) { clientMenus.pokemon.glow = { type: 'bool', sub: {}, perm: ['pokemon'] } } - if (dbSelection('pokestop') === 'mad') { + if (dbSelection('pokestop').type === 'mad') { clientMenus.pokestops.madQuestText = { type: 'bool', perm: ['quests'] } } diff --git a/server/src/services/ui/primary.js b/server/src/services/ui/primary.js index 8eb92da32..553f8a9d7 100644 --- a/server/src/services/ui/primary.js +++ b/server/src/services/ui/primary.js @@ -1,5 +1,5 @@ /* eslint-disable no-restricted-syntax */ -const { database: { settings: { leagues } } } = require('../config') +const { api: { pvp: { leagues } } } = require('../config') module.exports = function generateUi(filters, perms) { const ui = {} @@ -21,16 +21,16 @@ module.exports = function generateUi(filters, perms) { ], secondary: [ { - name: 'level', label: '', min: 1, max: 35, perm: 'stats', + name: 'level', label: '', min: 1, max: 35, perm: 'iv', }, { - name: 'atk_iv', label: '', min: 0, max: 15, perm: 'stats', + name: 'atk_iv', label: '', min: 0, max: 15, perm: 'iv', }, { - name: 'def_iv', label: '', min: 0, max: 15, perm: 'stats', + name: 'def_iv', label: '', min: 0, max: 15, perm: 'iv', }, { - name: 'sta_iv', label: '', min: 0, max: 15, perm: 'stats', + name: 'sta_iv', label: '', min: 0, max: 15, perm: 'iv', }, ], } @@ -42,7 +42,7 @@ module.exports = function generateUi(filters, perms) { if (!ui.wayfarer) ui.wayfarer = {} ui.wayfarer[key] = true; break case 'spawnpoints': - case 's2cells': + case 'scanCells': case 'devices': if (!ui.admin) ui.admin = {} ui.admin[key] = true; break @@ -62,7 +62,7 @@ module.exports = function generateUi(filters, perms) { case 'submissionCells': case 'portals': ui.wayfarer[key] = true; break case 'spawnpoints': - case 's2cells': + case 'scanCells': case 'devices': ui.admin[key] = true; break case 'scanAreas': case 'weather': ui[key].enabled = true; break diff --git a/server/src/services/ui/webhook.js b/server/src/services/ui/webhook.js index 596618771..dd1855ca7 100644 --- a/server/src/services/ui/webhook.js +++ b/server/src/services/ui/webhook.js @@ -47,15 +47,15 @@ module.exports = function webhookUi(provider, hookConfig, pvp, leagues) { primary: { sliders: [ { name: 'iv', label: '', min: -1, max: 100, perm: 'iv', low: 'min_iv', high: 'max_iv' }, - { name: 'level', label: '', min: 0, max: 40, perm: 'stats', low: 'min_level', high: 'max_level' }, + { name: 'level', label: '', min: 0, max: 40, perm: 'iv', low: 'min_level', high: 'max_level' }, ], }, advanced: { sliders: [ - { name: 'cp', label: '', min: 0, max: 9000, perm: 'stats', low: 'min_cp', high: 'max_cp' }, - { name: 'atk_iv', label: '', min: 0, max: 15, perm: 'stats', low: 'atk', high: 'max_atk' }, - { name: 'def_iv', label: '', min: 0, max: 15, perm: 'stats', low: 'def', high: 'max_def' }, - { name: 'sta_iv', label: '', min: 0, max: 15, perm: 'stats', low: 'sta', high: 'max_sta' }, + { name: 'cp', label: '', min: 0, max: 9000, perm: 'iv', low: 'min_cp', high: 'max_cp' }, + { name: 'atk_iv', label: '', min: 0, max: 15, perm: 'iv', low: 'atk', high: 'max_atk' }, + { name: 'def_iv', label: '', min: 0, max: 15, perm: 'iv', low: 'def', high: 'max_def' }, + { name: 'sta_iv', label: '', min: 0, max: 15, perm: 'iv', low: 'sta', high: 'max_sta' }, ], texts: [{ name: 'min_time', type: 'number', max: 60, adornment: 's', xs: 4, sm: 4, width: 100 }], booleans: [ diff --git a/server/src/strategies/discord.js b/server/src/strategies/discord.js index 2983b444a..3d4fa2bab 100644 --- a/server/src/strategies/discord.js +++ b/server/src/strategies/discord.js @@ -6,7 +6,10 @@ const path = require('path') // if writing a custom strategy, rename 'discord' below to your strategy name // this will automatically grab all of its unique values in the config -const { map: { forceTutorial }, discord: strategyConfig } = require('../services/config') +const { + map: { forceTutorial }, + authentication: { discord: strategyConfig, perms }, +} = require('../services/config') const { User } = require('../models/index') const DiscordMapClient = require('../services/DiscordClient') const logUserAuth = require('../services/logUserAuth') @@ -26,7 +29,7 @@ client.on('ready', () => { client.login(strategyConfig.botToken) -const MapClient = new DiscordMapClient(client, strategyConfig) +const MapClient = new DiscordMapClient(client, { ...strategyConfig, perms }) const authHandler = async (req, accessToken, refreshToken, profile, done) => { if (!req.query.code) { diff --git a/server/src/strategies/local.js b/server/src/strategies/local.js index c286095d1..99e00337e 100644 --- a/server/src/strategies/local.js +++ b/server/src/strategies/local.js @@ -6,18 +6,26 @@ const path = require('path') // if writing a custom strategy, rename 'local' below to your strategy name // this will automatically grab all of its unique values in the config -const { map: { forceTutorial }, local: strategyConfig, discord, alwaysEnabledPerms } = require('../services/config') +const { + map: { forceTutorial }, + authentication: { local: strategyConfig, alwaysEnabledPerms, perms }, +} = require('../services/config') const { User } = require('../models/index') const Utility = require('../services/Utility') +if (strategyConfig.doNothing) { + // This is for nothing other than demonstrating how to implement a custom local strategy with the above instructions +} + const authHandler = async (req, username, password, done) => { + const localPerms = Object.keys(perms).filter(key => perms[key].roles.includes('local')) const user = { perms: { ...Object.fromEntries( - Object.keys(discord.perms) - .map(x => [x, strategyConfig.perms.includes(x) || alwaysEnabledPerms.includes(x)]), + Object.keys(perms) + .map(perm => [perm, localPerms.includes(perm) || alwaysEnabledPerms.includes(perm)]), ), - areaRestrictions: Utility.areaPerms(strategyConfig.perms, 'local'), + areaRestrictions: Utility.areaPerms(localPerms, 'local'), webhooks: [], }, } @@ -42,9 +50,9 @@ const authHandler = async (req, username, password, done) => { } } if (bcrypt.compareSync(password, userExists.password)) { - ['discordPerms', 'telegramPerms'].forEach((perms) => { - if (userExists[perms]) { - user.perms = Utility.mergePerms(user.perms, JSON.parse(userExists[perms])) + ['discordPerms', 'telegramPerms'].forEach((permSet) => { + if (userExists[permSet]) { + user.perms = Utility.mergePerms(user.perms, userExists[permSet]) } }) if (userExists.strategy !== 'local') { diff --git a/server/src/strategies/telegram.js b/server/src/strategies/telegram.js index a7919fc75..ccb27e6c9 100644 --- a/server/src/strategies/telegram.js +++ b/server/src/strategies/telegram.js @@ -5,7 +5,10 @@ const path = require('path') // if writing a custom strategy, rename 'telegram' below to your strategy name // this will automatically grab all of its unique values in the config -const { map: { forceTutorial }, telegram: strategyConfig, alwaysEnabledPerms } = require('../services/config') +const { + map: { forceTutorial }, + authentication: { telegram: strategyConfig, perms, alwaysEnabledPerms }, +} = require('../services/config') const { User } = require('../models/index') const Fetch = require('../services/Fetch') const Utility = require('../services/Utility') @@ -14,7 +17,7 @@ const authHandler = async (req, profile, done) => { const user = { ...profile, perms: { - ...Object.fromEntries(Object.keys(strategyConfig.perms).map(x => [x, false])), + ...Object.fromEntries(Object.keys(perms).map(x => [x, false])), areaRestrictions: [], webhooks: [], }, @@ -36,7 +39,7 @@ const authHandler = async (req, profile, done) => { } })) - Object.entries(strategyConfig.perms).forEach(([perm, info]) => { + Object.entries(perms).forEach(([perm, info]) => { if (info.enabled && (alwaysEnabledPerms.includes(perm) || info.roles.some(role => groupInfo.includes(role)))) { user.perms[perm] = true diff --git a/src/components/Clustering.jsx b/src/components/Clustering.jsx index 068d90dcc..25df20bd6 100644 --- a/src/components/Clustering.jsx +++ b/src/components/Clustering.jsx @@ -12,10 +12,10 @@ const getId = (component, item) => { case 'nests': return item.nest_id } } -const ignoredClustering = ['devices', 'submissionCells', 's2cells', 'weather'] +const ignoredClustering = ['devices', 'submissionCells', 'scanCells', 'weather'] export default function Clustering({ - category, renderedData, userSettings, clusterZoomLvl, staticUserSettings, params, + category, renderedData, userSettings, clusteringRules, staticUserSettings, params, filters, map, Icons, perms, tileStyle, config, userIcons, setParams, isNight, }) { const Component = index[category] @@ -58,14 +58,14 @@ export default function Clustering({ return null }) - const limitHit = finalData.length > config.clusterZoomLevels.forcedClusterLimit + const limitHit = finalData.length > clusteringRules.forcedLimit && !ignoredClustering.includes(category) - return limitHit || (clusterZoomLvl && userSettings.clustering) ? ( + return limitHit || (clusteringRules.zoomLevel && userSettings.clustering) ? ( <> {finalData} @@ -77,7 +77,7 @@ export default function Clustering({ messages={[ { key: 'limitHit', - variables: [category, config.clusterZoomLevels.forcedClusterLimit], + variables: [category, clusteringRules.forcedLimit], }, { key: 'zoomIn', diff --git a/src/components/ConfigSettings.jsx b/src/components/ConfigSettings.jsx index 1a1e3daf7..f1bef75fd 100644 --- a/src/components/ConfigSettings.jsx +++ b/src/components/ConfigSettings.jsx @@ -95,6 +95,16 @@ export default function ConfigSettings({ const cached = localState.state.settings.localeSelection const i18cached = localStorage.getItem('i18nextLng') localState.state.settings.localeSelection = cached !== i18cached ? i18cached : cached + + const validNav = Object.keys(serverSettings.config.navigation) + localState.state.settings.navigation = validNav.includes(localState.state.settings.navigation) + ? localState.state.settings.navigation + : serverSettings.config.navigation[validNav[0]]?.name + + const validTs = Object.keys(serverSettings.config.tileServers) + localState.state.settings.tileServers = validTs.includes(localState.state.settings.tileServers) + ? localState.state.settings.tileServers + : serverSettings.config.tileServers[validTs[0]]?.name } else { serverSettings.settings.localeSelection = localStorage.getItem('i18nextLng') || serverSettings.settings.localeSelection } diff --git a/src/components/Map.jsx b/src/components/Map.jsx index ffa58fd1b..f246bb37f 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -10,12 +10,12 @@ import Webhook from './layout/dialogs/webhooks/Webhook' const userSettingsCategory = category => { switch (category) { - default: return category case 'devices': case 'spawnpoints': - case 's2cells': return 'admin' + case 'scanCells': return 'admin' case 'submissionCells': case 'portals': return 'wayfarer' + default: return category } } @@ -79,9 +79,9 @@ export default function Map({ serverSettings: { config: { map: config, tileServe return ( <> OpenStreetMap, under ODbL.'} + url={tileServer?.url || 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png'} minZoom={config.minZoom} maxZoom={config.maxZoom} /> @@ -155,8 +155,8 @@ export default function Map({ serverSettings: { config: { map: config, tileServe userIcons={icons} userSettings={userSettings[userSettingsCategory(category)] || {}} filters={filters[category]} - tileStyle={tileServer.style} - clusterZoomLvl={config.clusterZoomLevels[category]} + tileStyle={tileServer?.style || 'light'} + clusteringRules={config.clustering[category] || { zoomLimit: config.minZoom, forcedLimit: 10000 }} staticUserSettings={staticUserSettings[category]} params={manualParams} setParams={setManualParams} diff --git a/src/components/QueryData.jsx b/src/components/QueryData.jsx index a3d9d2095..f4644c725 100644 --- a/src/components/QueryData.jsx +++ b/src/components/QueryData.jsx @@ -14,7 +14,7 @@ const getPolling = category => { switch (category) { case 'devices': case 'gyms': - case 's2cells': + case 'scanCells': return 10 * 1000 case 'pokemon': return 20 * 1000 @@ -25,7 +25,7 @@ const getPolling = category => { } export default function QueryData({ - bounds, onMove, map, tileStyle, clusterZoomLvl, config, params, + bounds, onMove, map, tileStyle, clusteringRules, config, params, category, available, filters, staticFilters, staticUserSettings, sizeKey, userSettings, perms, Icons, userIcons, setParams, isNight, setExcludeList, }) { @@ -87,7 +87,7 @@ export default function QueryData({ }, [filters, userSettings]) const { data, previousData, refetch, error } = useQuery(Query[category]( - filters, perms, map.getZoom(), clusterZoomLvl, + filters, perms, map.getZoom(), clusteringRules.zoomLevel, ), { context: { timeout: getPolling(category) }, variables: { @@ -119,7 +119,7 @@ export default function QueryData({ setWebhookMode(false), icon: 'Close', color: 'primary' }, ] - if (map.enableFeedback) { + if (map.feedbackLink) { footerButtons.unshift( { name: 'feedback', action: () => setFeedback(true), icon: 'BugReport', disabled: !webhookData[selectedWebhook].human, color: '#00e676' }, ) diff --git a/src/components/layout/drawer/Areas.jsx b/src/components/layout/drawer/Areas.jsx index f4be7947f..a861d31e6 100644 --- a/src/components/layout/drawer/Areas.jsx +++ b/src/components/layout/drawer/Areas.jsx @@ -29,8 +29,8 @@ export default function AreaDropDown({ scanAreasZoom, manualAreas }) { { - const { lat, lon } = manualAreas[area] - map.flyTo([lat, lon], scanAreasZoom) + const { lat, lon, zoom } = manualAreas[area] + map.flyTo([lat, lon], zoom || scanAreasZoom) }} > diff --git a/src/components/layout/drawer/Settings.jsx b/src/components/layout/drawer/Settings.jsx index 755862143..02a11f931 100644 --- a/src/components/layout/drawer/Settings.jsx +++ b/src/components/layout/drawer/Settings.jsx @@ -229,7 +229,7 @@ export default function Settings({ Icons }) { - {config.map.enableStats + {config.map.statsLink && (