diff --git a/.configref b/.configref index 8de8d6b34..a75c11915 100644 --- a/.configref +++ b/.configref @@ -1 +1 @@ -18036 \ No newline at end of file +18095 \ No newline at end of file diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f376423b1..d8e61bd4b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -17,10 +17,16 @@ jobs: fetch-depth: 2 - name: Get changed files id: changed-files - uses: tj-actions/changed-files@v19 + uses: tj-actions/changed-files@v32 with: files: | server/src/configs/default.json + - name: Setup Node.js environment + if: steps.changed-files.outputs.any_changed == 'true' + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' - name: Run script if: steps.changed-files.outputs.any_changed == 'true' run: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6faf6bc72..f045feba8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,5 +1,5 @@ name: Docker -on: [push, release] +on: [push] env: REGISTRY: ghcr.io @@ -10,18 +10,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v32 + with: + files: | + server/src/configs/*.json - name: Setup Node.js environment - uses: actions/setup-node@v2 + if: steps.changed-files.outputs.any_changed == 'true' + uses: actions/setup-node@v3 with: node-version: 16 - cache: "yarn" + cache: 'yarn' - name: Generate latest env vars + if: steps.changed-files.outputs.any_changed == 'true' run: | yarn gen-env-config - name: Commit and push changes + if: steps.changed-files.outputs.any_changed == 'true' uses: devops-infra/action-commit-push@v0.9.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -31,33 +40,32 @@ jobs: needs: Sync runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Set .gitsha - if: github.event_name == 'push' - run: "echo ${{github.sha}} > .gitsha" - - name: Set .gitref - if: github.event_name == 'push' - run: "echo ${{github.ref}} > .gitref" + - uses: actions/checkout@v3 - - name: Log in to the Container registry - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Set .gitsha + if: github.event_name == 'push' + run: 'echo ${{github.sha}} > .gitsha' + - name: Set .gitref + if: github.event_name == 'push' + run: 'echo ${{github.ref}} > .gitref' - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image - uses: docker/build-push-action@v2 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Build and push Docker image + uses: docker/build-push-action@v3 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 758f5878c..dcf26c268 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: with: fetch-depth: 2 - name: Setup Node.js environment - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 16 cache: 'yarn' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 207898138..1798a5d5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: with: fetch-depth: 2 - name: Setup Node.js environment - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 16 cache: 'yarn' diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 0dfc7fdb9..e86760e4b 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -6,29 +6,54 @@ services: command: sh -c "yarn start" restart: unless-stopped environment: + # All database values are ignored if you are using a `local.json` file! + + # Your Scanner Database SCANNER_DB_HOST: 127.0.0.1 SCANNER_DB_PORT: 3306 SCANNER_DB_USERNAME: scanner_username SCANNER_DB_PASSWORD: scanner_user_pw - SCANNER_DB_NAME: realdevicemap + SCANNER_DB_NAME: scanner_db + + # Your ReactMap Database + # optional, but recommended, if omitted it will default to your manual database + REACT_MAP_DB_HOST: 127.0.0.1 + REACT_MAP_DB_PORT: 3306 + REACT_MAP_DB_USERNAME: react_map_username + REACT_MAP_DB_PASSWORD: react_map_user_pw + REACT_MAP_DB_NAME: react_map_db + + # Your Manual Database (Optional - Nests & Portals) MANUAL_DB_HOST: 127.0.0.1 MANUAL_DB_PORT: 3306 MANUAL_DB_USERNAME: manual_username MANUAL_DB_PASSWORD: manual_user_pw MANUAL_DB_NAME: manual_db + + # Other config values - the below env vars will override anything, including `local.json` + # More config values you can add: + # https://github.com/WatWowMap/ReactMap/blob/main/server/src/configs/custom-environment-variables.json + # Config wiki page: + # https://github.com/WatWowMap/ReactMap/wiki/04.-Full-Config-Explanation + # Devs recommened that you use a `local.json` config file though instead of env variables! MAP_GENERAL_TITLE: ReactMap MAP_GENERAL_START_LAT: 0 MAP_GENERAL_START_LON: 0 - ARRAY_VALUE_EXAMPLE: "[3, 4, 5]" + # ARRAY_VALUE_EXAMPLE: "[3, 4, 5]" + volumes: + # All of these are optional - comment out whichever ones you aren't using - ./server/src/configs/areas.json:/home/node/server/src/configs/areas.json - ./server/src/configs/local.json:/home/node/server/src/configs/local.json - ./server/src/configs/geofence.json/:/home/node/server/src/configs/geofence.json - ./example.env:/home/node/.env + security_opt: - - no-new-privileges:true #https://nodramadevops.com/2019/06/running-docker-application-containers-more-securely/ + # https://nodramadevops.com/2019/06/running-docker-application-containers-more-securely/ + - no-new-privileges:true + ports: - - "9090:8080" + - '9090:8080' # nginx: # image: nginx # container_name: nginx diff --git a/ecosystem.config.example.js b/ecosystem.config.example.js new file mode 100644 index 000000000..9608351b1 --- /dev/null +++ b/ecosystem.config.example.js @@ -0,0 +1,41 @@ +module.exports = { + apps: [ + { + name: 'ReactMap', + script: 'ReactMap.js', + instances: 1, + autorestart: false, + cron_restart: '*/60 */24 * * *', + exec_mode: 'fork', + max_memory_restart: '1G', + env_production: { + NODE_ENV: 'production', + }, + }, + // Advanced, comment out or remove the above block if you uncomment these two + // { + // name: 'RM_Client', + // script: 'yarn build && yarn generate', + // instances: 1, + // autorestart: false, + // cron_restart: '*/60 */24 * * *', + // exec_mode: 'fork', + // max_memory_restart: '1G', + // env_production: { + // NODE_ENV: 'production', + // }, + // }, + // { + // name: 'RM_Server', + // script: 'server/src/index.js', + // instances: 4, + // cron_restart: '*/60 */24 * * *', + // exec_mode: 'cluster', + // autorestart: true, + // max_memory_restart: '4G', + // env_production: { + // NODE_ENV: 'production', + // }, + // }, + ], +} diff --git a/esbuild.config.js b/esbuild.config.js index b19a4bde2..9bd4a3c07 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -11,12 +11,11 @@ const esbuildMxnCopy = require('esbuild-plugin-mxn-copy') const aliasPlugin = require('esbuild-plugin-path-alias') const { eslintPlugin } = require('esbuild-plugin-eslinter') +const pkg = require('./package.json') + const env = fs.existsSync(resolve(__dirname, '.env')) ? dotenv.config() : { parsed: process.env } -const { version } = JSON.parse( - fs.readFileSync(resolve(__dirname, 'package.json')), -) const isDevelopment = Boolean(process.argv.includes('--dev')) const isRelease = Boolean(process.argv.includes('--release')) const isServing = Boolean(process.argv.includes('--serve')) @@ -112,7 +111,7 @@ ${customPaths.map((x, i) => ` ${i + 1}. src/${x.split('src/')[1]}`).join('\n')} }, }) } - console.log(`[BUILD] Building production version: ${version}`) + console.log(`[BUILD] Building production version: ${pkg.version}`) } const esbuild = { @@ -121,7 +120,7 @@ const esbuild = { bundle: true, outdir: 'dist/', publicPath: '/', - entryNames: isDevelopment ? undefined : `[name]-${version}-[hash]`, + entryNames: isDevelopment ? undefined : `[name]-${pkg.version}-[hash]`, metafile: true, minify: env.parsed.NO_MINIFIED ? false : isRelease || !isDevelopment, logLevel: isDevelopment ? 'info' : 'error', @@ -143,7 +142,7 @@ const esbuild = { SENTRY_DSN: env.parsed.SENTRY_DSN || '', SENTRY_TRACES_SAMPLE_RATE: env.parsed.SENTRY_TRACES_SAMPLE_RATE || 0.1, SENTRY_DEBUG: env.parsed.SENTRY_DEBUG || false, - VERSION: version, + VERSION: pkg.version, DEVELOPMENT: isDevelopment, CUSTOM: hasCustom, LOCALES: fs.readdirSync(resolve(__dirname, 'public/locales')), diff --git a/package.json b/package.json index 5017cfc52..c97682128 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactmap", - "version": "1.6.5", + "version": "1.6.8", "description": "React based frontend map.", "main": "ReactMap.js", "author": "TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com>", @@ -25,8 +25,8 @@ "migrate:latest": "knex --knexfile server/knexfile.cjs migrate:latest", "migrate:rollback": "knex --knexfile server/knexfile.cjs migrate:rollback", "release": "node server/scripts/newRelease.js", - "prettier:check": "prettier --check \"**/*.{css,html,js,jsx}\"", - "prettier:fix": "prettier --write \"**/*.{css,html,js,jsx}\"", + "prettier:check": "prettier --check \"**/*.{css,html,js,jsx,yml}\"", + "prettier:fix": "prettier --write \"**/*.{css,html,js,jsx,yml}\"", "eslint:check": "eslint \"**/*.{js,jsx}\"", "eslint:fix": "eslint \"**/*.{js,jsx}\" --fix" }, @@ -113,7 +113,7 @@ "react-virtualized-auto-sizer": "^1.0.5", "react-window": "^1.8.6", "require-from-string": "^2.0.2", - "suncalc": "^1.8.0", + "suncalc": "^1.9.0", "zustand": "^4.0.0-rc.1" } } \ No newline at end of file diff --git a/public/base-locales/en.json b/public/base-locales/en.json index 958786ec5..60de2b8ae 100644 --- a/public/base-locales/en.json +++ b/public/base-locales/en.json @@ -558,5 +558,7 @@ "seen_nearby_cell": "Seen in this Cell", "seen_lure_encounter": "Lure Encounter", "seen_lure_wild": "Lure Spawn", - "seen_wild": "Wild Spawn" -} + "seen_wild": "Wild Spawn", + "event_stops": "Event Stops", + "event_stop_timers": "Event Stop Timers" +} \ No newline at end of file diff --git a/server/src/configs/custom-environment-variables.json b/server/src/configs/custom-environment-variables.json index e47e3fabf..0b5c9d3af 100644 --- a/server/src/configs/custom-environment-variables.json +++ b/server/src/configs/custom-environment-variables.json @@ -565,6 +565,10 @@ "__name": "CLIENT_SIDE_OPTIONS_POKESTOPS_LURE_TIMERS", "__format": "boolean" }, + "eventStopTimers": { + "__name": "CLIENT_SIDE_OPTIONS_POKESTOPS_EVENT_STOP_TIMERS", + "__format": "boolean" + }, "interactionRanges": { "__name": "CLIENT_SIDE_OPTIONS_POKESTOPS_INTERACTION_RANGES", "__format": "boolean" @@ -760,6 +764,10 @@ "__name": "DEFAULT_FILTERS_POKESTOPS_QUESTS", "__format": "boolean" }, + "eventStops": { + "__name": "DEFAULT_FILTERS_POKESTOPS_EVENT_STOPS", + "__format": "boolean" + }, "questSet": "DEFAULT_FILTERS_POKESTOPS_QUEST_SET", "items": { "__name": "DEFAULT_FILTERS_POKESTOPS_ITEMS", diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 1e3540e5a..cc548d46b 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -300,6 +300,7 @@ "clustering": true, "invasionTimers": false, "lureTimers": false, + "eventStopTimers": false, "interactionRanges": false, "lureRange": false, "madQuestText": false, @@ -419,6 +420,7 @@ "levels": "all", "lures": true, "quests": true, + "eventStops": false, "questSet": "both", "items": true, "megaEnergy": true, diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index e2a8f999a..1a426591f 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -11,7 +11,8 @@ module.exports = { JSON: GraphQLJSON, Query: { available: (_, _args, { Event, Db, perms, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') const available = { pokemon: perms.pokemon ? Event.available.pokemon : [], @@ -27,7 +28,8 @@ module.exports = { } }, badges: async (_, _args, { req, perms, Db, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.gymBadges) { @@ -37,7 +39,8 @@ module.exports = { return [] }, devices: (_, args, { perms, Db, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.devices) { @@ -46,7 +49,8 @@ module.exports = { return [] }, geocoder: (_, args, { perms, serverV, clientV, Event }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.webhooks) { @@ -58,7 +62,8 @@ module.exports = { return [] }, gyms: (_, args, { req, perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.gyms || perms?.raids) { @@ -67,7 +72,8 @@ module.exports = { return [] }, gymsSingle: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.[args.perm]) { @@ -76,7 +82,8 @@ module.exports = { return {} }, nests: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.nests) { @@ -85,7 +92,8 @@ module.exports = { return [] }, nestsSingle: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.[args.perm]) { @@ -94,7 +102,8 @@ module.exports = { return {} }, pokestops: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if ( @@ -108,7 +117,8 @@ module.exports = { return [] }, pokestopsSingle: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.[args.perm]) { @@ -117,7 +127,8 @@ module.exports = { return {} }, pokemon: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.pokemon) { @@ -129,7 +140,8 @@ module.exports = { return [] }, pokemonSingle: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.[args.perm]) { @@ -138,7 +150,8 @@ module.exports = { return {} }, portals: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.portals) { @@ -147,7 +160,8 @@ module.exports = { return [] }, portalsSingle: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.[args.perm]) { @@ -156,7 +170,8 @@ module.exports = { return {} }, scanCells: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.scanCells && args.zoom >= config.map.scanCellsZoom) { @@ -165,7 +180,8 @@ module.exports = { return [] }, scanAreas: (_, _args, { req, perms, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.scanAreas) { @@ -187,7 +203,8 @@ module.exports = { return [{ features: [] }] }, scanAreasMenu: (_, _args, { req, perms, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.scanAreas) { @@ -224,7 +241,8 @@ module.exports = { return [] }, search: async (_, args, { Event, perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') const { category, webhookName, search } = args @@ -266,7 +284,8 @@ module.exports = { return [] }, searchQuest: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') const { category, search } = args @@ -279,7 +298,8 @@ module.exports = { return [] }, spawnpoints: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.spawnpoints) { @@ -288,7 +308,8 @@ module.exports = { return [] }, submissionCells: async (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if ( @@ -311,7 +332,8 @@ module.exports = { return [{ placementCells: [], typeCells: [] }] }, weather: (_, args, { perms, serverV, clientV, Db }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.weather) { @@ -320,7 +342,8 @@ module.exports = { return [] }, webhook: (_, args, { req, perms, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') if (perms?.webhooks) { @@ -334,7 +357,8 @@ module.exports = { return {} }, scanner: (_, args, { req, perms, serverV, clientV }) => { - if (clientV !== serverV) throw new UserInputError('old_client') + if (clientV && serverV && clientV !== serverV) + throw new UserInputError('old_client', { clientV, serverV }) if (!perms) throw new AuthenticationError('session_expired') const { category, method, data } = args diff --git a/server/src/index.js b/server/src/index.js index 423a0bd6b..745eccc25 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -19,7 +19,7 @@ const { sessionStore } = require('./services/sessionStore') const rootRouter = require('./routes/rootRouter') const typeDefs = require('./graphql/typeDefs') const resolvers = require('./graphql/resolvers') -const { version } = require('../../package.json') +const pkg = require('../../package.json') if (!config.devOptions.skipUpdateCheck) { require('./services/checkForUpdates') @@ -41,8 +41,9 @@ const server = new ApolloServer({ Db, Event, perms, - serverV: version, - clientV: req.headers['apollographql-client-version']?.trim() || '', + serverV: pkg.version || 1, + clientV: + req.headers['apollographql-client-version']?.trim() || pkg.version || 1, } }, formatError: (e) => { @@ -55,11 +56,12 @@ const server = new ApolloServer({ e?.message.includes('skipUndefined()') || e?.message === 'old_client' ) { - if (config.devOptions.enabled) { - console.log( - '[GQL] Old client detected, forcing user to refresh, no need to report this error unless it continues to happen', - ) - } + console.log( + '[GQL] Old client detected, forcing user to refresh, no need to report this error unless it continues to happen\nClient:', + e.extensions.clientV, + 'Server:', + e.extensions.serverV, + ) return { message: 'old_client' } } if (e.message === 'session_expired') { diff --git a/server/src/models/Device.js b/server/src/models/Device.js index ccb05dca0..6d0a3c265 100644 --- a/server/src/models/Device.js +++ b/server/src/models/Device.js @@ -25,7 +25,7 @@ module.exports = class Device extends Model { ]) } else { query - .join('instance', 'device.instance_name', 'instance.name') + .leftJoin('instance', 'device.instance_name', 'instance.name') .select( 'uuid AS id', 'last_seen AS updated', @@ -42,6 +42,11 @@ module.exports = class Device extends Model { ) { return [] } - return query.from(settings.isMad ? 'settings_device' : 'device') + const results = await query.from( + settings.isMad ? 'settings_device' : 'device', + ) + return results.filter( + (device) => device.id && device.last_lat && device.last_lon, + ) } } diff --git a/server/src/models/Pokestop.js b/server/src/models/Pokestop.js index f042e6766..aa7b37524 100644 --- a/server/src/models/Pokestop.js +++ b/server/src/models/Pokestop.js @@ -70,6 +70,7 @@ module.exports = class Pokestop extends Model { onlyInvasions, onlyArEligible, onlyAllPokestops, + onlyEventStops, onlyAreas = [], }, ts, @@ -515,11 +516,24 @@ module.exports = class Pokestop extends Model { ar.where(isMad ? 'is_ar_scan_eligible' : 'ar_scan_eligible', 1) }) } + if (onlyEventStops && pokestopPerms) { + stops.orWhere((event) => { + event + .where('display_type', 7) + .andWhere('character', 0) + .andWhere( + multiInvasionMs ? 'expiration_ms' : 'expiration', + '>=', + safeTs * (multiInvasionMs ? 1000 : 1), + ) + }) + } }) } else if (onlyLevels !== 'all' && hasPowerUp) { query.andWhere('power_up_level', onlyLevels) } const results = await query + const normalized = isMad ? this.mapMAD(results, safeTs) : this.mapRDM(results, safeTs) @@ -571,16 +585,25 @@ module.exports = class Pokestop extends Model { 'power_up_level', 'power_up_end_timestamp', ]) + if (filters.onlyEventStops) { + filtered.invasions = pokestop.invasions.filter( + (invasion) => !invasion.grunt_type, + ) + } } if ( perms.invasions && (filters.onlyAllPokestops || filters.onlyInvasions) ) { - filtered.invasions = filters.onlyAllPokestops - ? pokestop.invasions - : pokestop.invasions.filter( - (invasion) => filters[`i${invasion.grunt_type}`], - ) + filtered.invasions = [ + ...(Array.isArray(filtered.invasions) ? filtered.invasions : []), + ...(filters.onlyAllPokestops + ? pokestop.invasions + : pokestop.invasions.filter( + (invasion) => + invasion.grunt_type && filters[`i${invasion.grunt_type}`], + )), + ] } if ( perms.lures && @@ -715,7 +738,10 @@ module.exports = class Pokestop extends Model { } }) } - if (invasion.grunt_type && invasion.incident_expire_timestamp >= ts) { + if ( + typeof invasion.grunt_type === 'number' && + invasion.incident_expire_timestamp >= ts + ) { filtered[result.id].invasions.push(invasion) } filtered[result.id].quests.push(quest) @@ -755,7 +781,10 @@ module.exports = class Pokestop extends Model { if (altQuest.quest_reward_type) { filtered[result.id].quests.push(altQuest) } - if (invasion.grunt_type && invasion.incident_expire_timestamp >= ts) { + if ( + typeof invasion.grunt_type === 'number' && + invasion.incident_expire_timestamp >= ts + ) { filtered[result.id].invasions.push(invasion) } } @@ -1061,7 +1090,8 @@ module.exports = class Pokestop extends Model { queries.invasions = this.query() .leftJoin('incident', 'pokestop.id', 'incident.pokestop_id') .distinct('incident.character AS grunt_type') - .where( + .where('incident.character', '>', 0) + .andWhere( multiInvasionMs ? 'expiration_ms' : 'incident.expiration', '>=', ts * (multiInvasionMs ? 1000 : 1), @@ -1071,10 +1101,16 @@ module.exports = class Pokestop extends Model { queries.invasions = this.query() .distinct(isMad ? 'incident_grunt_type AS grunt_type' : 'grunt_type') .where( + isMad ? 'incident_grunt_type AS grunt_type' : 'grunt_type', + '>', + 0, + ) + .andWhere( isMad ? 'incident_expiration' : 'incident_expire_timestamp', '>=', isMad ? this.knex().fn.now() : ts, ) + .orderBy('grunt_type') } // invasions @@ -1096,6 +1132,7 @@ module.exports = class Pokestop extends Model { Object.entries(queries).map(async ([key, query]) => [key, await query]), ), ) + let questTypes = [ ...new Set([ ...(await this.query() diff --git a/server/src/services/config.js b/server/src/services/config.js index 5478bfe46..fb3b55f2e 100644 --- a/server/src/services/config.js +++ b/server/src/services/config.js @@ -40,6 +40,11 @@ if (!fs.existsSync(resolve(`${__dirname}/../configs/local.json`))) { SCANNER_DB_NAME, SCANNER_DB_USERNAME, SCANNER_DB_PASSWORD, + REACT_MAP_DB_HOST, + REACT_MAP_DB_PORT, + REACT_MAP_DB_USERNAME, + REACT_MAP_DB_PASSWORD, + REACT_MAP_DB_NAME, MANUAL_DB_HOST, MANUAL_DB_PORT, MANUAL_DB_NAME, @@ -49,13 +54,26 @@ if (!fs.existsSync(resolve(`${__dirname}/../configs/local.json`))) { MAP_GENERAL_START_LON, } = process.env - if ( + const hasScannerDb = SCANNER_DB_HOST && SCANNER_DB_PORT && SCANNER_DB_NAME && SCANNER_DB_USERNAME && SCANNER_DB_PASSWORD - ) { + const hasReactMapDb = + REACT_MAP_DB_HOST && + REACT_MAP_DB_PORT && + REACT_MAP_DB_USERNAME && + REACT_MAP_DB_PASSWORD && + REACT_MAP_DB_NAME + const hasManualDb = + MANUAL_DB_HOST && + MANUAL_DB_PORT && + MANUAL_DB_NAME && + MANUAL_DB_USERNAME && + MANUAL_DB_PASSWORD + + if (hasScannerDb) { config.database.schemas.push({ host: SCANNER_DB_HOST, port: +SCANNER_DB_PORT, @@ -77,20 +95,30 @@ if (!fs.existsSync(resolve(`${__dirname}/../configs/local.json`))) { 'Missing scanner database config! \nCheck to make sure you have SCANNER_DB_HOST,SCANNER_DB_PORT, SCANNER_DB_NAME, SCANNER_DB_USERNAME, and SCANNER_DB_PASSWORD', ) } - if ( - MANUAL_DB_HOST && - MANUAL_DB_PORT && - MANUAL_DB_NAME && - MANUAL_DB_USERNAME && - MANUAL_DB_PASSWORD - ) { + if (hasReactMapDb) { + config.database.schemas.push({ + host: REACT_MAP_DB_HOST, + port: +REACT_MAP_DB_PORT, + database: REACT_MAP_DB_USERNAME, + username: REACT_MAP_DB_PASSWORD, + password: REACT_MAP_DB_NAME, + useFor: ['session', 'user'], + }) + } else { + console.log( + 'Missing ReactMap specific table, attempting to use the manual database instead.', + ) + } + if (hasManualDb) { config.database.schemas.push({ host: MANUAL_DB_HOST, port: +MANUAL_DB_PORT, database: MANUAL_DB_NAME, username: MANUAL_DB_USERNAME, password: MANUAL_DB_PASSWORD, - useFor: ['session', 'user', 'nest', 'portal'], + useFor: hasReactMapDb + ? ['nest', 'portal'] + : ['session', 'user', 'nest', 'portal'], }) } else { console.error( diff --git a/server/src/services/defaultFilters/buildDefaultFilters.js b/server/src/services/defaultFilters/buildDefaultFilters.js index a20c5a3c8..bb4967f53 100644 --- a/server/src/services/defaultFilters/buildDefaultFilters.js +++ b/server/src/services/defaultFilters/buildDefaultFilters.js @@ -64,6 +64,9 @@ module.exports = function buildDefault(perms, available, dbModels) { ? defaultFilters.pokestops.levels : undefined, lures: perms.lures ? defaultFilters.pokestops.lures : undefined, + eventStops: perms.pokestops + ? defaultFilters.pokestops.eventStops + : undefined, quests: perms.quests ? defaultFilters.pokestops.quests : undefined, showQuestSet: defaultFilters.pokestops.questSet, invasions: perms.invasions diff --git a/server/src/services/ui/clientOptions.js b/server/src/services/ui/clientOptions.js index a1bb162f0..7a788e4a7 100644 --- a/server/src/services/ui/clientOptions.js +++ b/server/src/services/ui/clientOptions.js @@ -26,6 +26,7 @@ module.exports = function clientOptions(perms) { clustering: { type: 'bool', perm: ['pokestops', 'quests', 'invasions'] }, invasionTimers: { type: 'bool', perm: ['invasions'] }, lureTimers: { type: 'bool', perm: ['lures'] }, + eventStopTimers: { type: 'bool', perm: ['pokestops'] }, interactionRanges: { type: 'bool', perm: ['pokestops'] }, lureRange: { type: 'bool', perm: ['lures'] }, hasQuestIndicator: { type: 'bool', perm: ['quests'] }, diff --git a/src/components/Clustering.jsx b/src/components/Clustering.jsx index 03bc11b1c..15e71c4d2 100644 --- a/src/components/Clustering.jsx +++ b/src/components/Clustering.jsx @@ -21,7 +21,7 @@ export default function Clustering({ config, userIcons, setParams, - isNight, + timeOfDay, onlyAreas, }) { const Component = index[category] @@ -59,7 +59,7 @@ export default function Clustering({ params={params} setParams={setParams} showCircles={showCircles} - isNight={isNight} + timeOfDay={timeOfDay} onlyAreas={onlyAreas} /> ) diff --git a/src/components/Container.jsx b/src/components/Container.jsx index 7b0b15da0..c28601fa0 100644 --- a/src/components/Container.jsx +++ b/src/components/Container.jsx @@ -14,7 +14,12 @@ export default function Container({ serverSettings, params, location, zoom }) { serverSettings.config.map.maxZoom + ? serverSettings.config.map.minZoom + : zoom + } zoomControl={false} preferCanvas > diff --git a/src/components/Map.jsx b/src/components/Map.jsx index e6228e141..1f3fa1a13 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -27,14 +27,15 @@ const userSettingsCategory = (category) => { } } -const getTileServer = (tileServers, settings, isNight) => { +const getTileServer = (tileServers, settings, timeOfDay) => { const fallbackTs = Object.values(tileServers).find( (server) => server.name !== 'auto', ) if (tileServers?.[settings.tileServers]?.name === 'auto') { - const autoTile = isNight - ? Object.values(tileServers).find((server) => server.style === 'dark') - : Object.values(tileServers).find((server) => server.style === 'light') + const autoTile = + timeOfDay === 'night' + ? Object.values(tileServers).find((server) => server.style === 'dark') + : Object.values(tileServers).find((server) => server.style === 'light') return autoTile || fallbackTs } return tileServers[settings.tileServers] || fallbackTs @@ -68,8 +69,8 @@ export default function Map({ const settings = useStore((state) => state.settings) const icons = useStore((state) => state.icons) const setLocation = useStore((s) => s.setLocation) - const isNight = useStatic((state) => state.isNight) - const setIsNight = useStatic((state) => state.setIsNight) + const timeOfDay = useStatic((state) => state.timeOfDay) + const setTimeOfDay = useStatic((state) => state.setTimeOfDay) const setZoom = useStore((state) => state.setZoom) const userSettings = useStore((state) => state.userSettings) @@ -93,14 +94,14 @@ export default function Map({ const newCenter = latLon || map.getCenter() setLocation([newCenter.lat, newCenter.lng]) setZoom(Math.floor(map.getZoom())) - setIsNight(Utility.nightCheck(newCenter.lat, newCenter.lng)) + setTimeOfDay(Utility.timeCheck(newCenter.lat, newCenter.lng)) }, [map], ) const tileServer = useMemo( - () => getTileServer(tileServers, settings, isNight), - [isNight, settings.tileServers], + () => getTileServer(tileServers, settings, timeOfDay), + [timeOfDay, settings.tileServers], ) const onFocus = () => setWindowState(true) @@ -142,6 +143,7 @@ export default function Map({ key={tileServer?.name} attribution={tileServer?.attribution || ''} url={ + tileServer?.[timeOfDay] || tileServer?.url || 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png' } @@ -201,6 +203,7 @@ export default function Map({ (filters[category].lures && value.lures) || (filters[category].invasions && value.invasions) || (filters[category].quests && value.quests) || + (filters[category].eventStops && value.eventStops) || (filters[category].arEligible && value.arEligible)) && !webhookMode ) { @@ -262,7 +265,7 @@ export default function Map({ staticUserSettings={staticUserSettings[category]} params={manualParams} setParams={setManualParams} - isNight={isNight} + timeOfDay={timeOfDay} isMobile={isMobile} setError={setError} active={active} diff --git a/src/components/QueryData.jsx b/src/components/QueryData.jsx index 85c419797..915e6895b 100644 --- a/src/components/QueryData.jsx +++ b/src/components/QueryData.jsx @@ -29,7 +29,7 @@ export default function QueryData({ Icons, userIcons, setParams, - isNight, + timeOfDay, setExcludeList, setError, active, @@ -151,13 +151,13 @@ export default function QueryData({ staticUserSettings={staticUserSettings} params={params} setParams={setParams} - isNight={isNight} + timeOfDay={timeOfDay} onlyAreas={onlyAreas} /> {category === 'weather' && ( {active.gameplay_condition} { const { pokemon: pokemonMod, weather: weatherMod } = Icons.modifiers let badge @@ -99,7 +99,7 @@ export const fancyMarker = ( }} > {pkmn.weather} { - filterId = `i${invasion.grunt_type}` - invasionIcons.unshift(Icons.getInvasions(invasion.grunt_type)) - invasionSizes.unshift(Icons.getSize('invasion', filters.filter[filterId])) - popupYOffset += rewardMod.offsetY - 1 - popupX += invasionMod.popupX - popupY += invasionMod.popupY + if (invasion.grunt_type) { + filterId = `i${invasion.grunt_type}` + invasionIcons.unshift(Icons.getInvasions(invasion.grunt_type)) + invasionSizes.unshift( + Icons.getSize('invasion', filters.filter[filterId]), + ) + popupYOffset += rewardMod.offsetY - 1 + popupX += invasionMod.popupX + popupY += invasionMod.popupY + } }) } if (hasQuest && !(hasInvasion && invasionMod?.removeQuest)) { diff --git a/src/components/markers/weather.jsx b/src/components/markers/weather.jsx index 140d53944..0be8ac1dc 100644 --- a/src/components/markers/weather.jsx +++ b/src/components/markers/weather.jsx @@ -2,7 +2,7 @@ import React from 'react' import { renderToString } from 'react-dom/server' import L from 'leaflet' -export default function weatherMarker(weather, Icons, isNight) { +export default function weatherMarker(weather, Icons, timeOfDay) { const { offsetX, offsetY, @@ -21,7 +21,7 @@ export default function weatherMarker(weather, Icons, isNight) { {weather.gameplay_condition}