diff --git a/package.json b/package.json index d5a98eca7..9fd85002f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactmap", - "version": "1.4.1", + "version": "1.5.0", "description": "React based frontend map.", "main": "ReactMap.mjs", "author": "TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com>", diff --git a/server/src/configs/custom-environment-variables.json b/server/src/configs/custom-environment-variables.json index f234bdc9e..cb4fbd113 100644 --- a/server/src/configs/custom-environment-variables.json +++ b/server/src/configs/custom-environment-variables.json @@ -938,6 +938,10 @@ "maxConnections": { "__name": "DATABASE_SETTINGS_MAX_CONNECTIONS", "__format": "number" + }, + "extraUserFields": { + "__name": "DATABASE_SETTINGS_EXTRA_USER_FIELDS", + "__format": "json" } }, "schemas": { diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 2aceb87ae..680b5d3bb 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -455,7 +455,8 @@ "gymBadgeTableName": "gymBadges", "sessionTableName": "session", "migrationTableName": "knex_migrations", - "maxConnections": 10 + "maxConnections": 10, + "extraUserFields": [] }, "schemas": [] }, diff --git a/server/src/db/migrations/20221006032139_add_data_column.cjs b/server/src/db/migrations/20221006032139_add_data_column.cjs new file mode 100644 index 000000000..49e4cceb8 --- /dev/null +++ b/server/src/db/migrations/20221006032139_add_data_column.cjs @@ -0,0 +1,22 @@ +/* eslint-disable no-unused-vars */ +const { database: { settings: { userTableName: tableName } } } = require('../../services/config') + +/** + * @typedef {import("knex")} Knex + */ + +/** + * @param {Knex} knex + */ +exports.up = async (knex) => knex.schema + .table(tableName, (table) => { + table.json('data') + }) + +/** + * @param {Knex} knex + */ +exports.down = async (knex) => knex.schema + .table(tableName, (table) => { + table.dropColumn('data') + }) diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index 41f1392f3..0af2c6265 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -388,6 +388,21 @@ module.exports = { const results = await User.query().where('username', args.username) return Boolean(results.length) }, + setExtraFields: async (_, { key, value }, { req }) => { + if (req.user?.id) { + const user = await User.query().findById(req.user.id) + if (user) { + const data = + typeof user.data === 'string' + ? JSON.parse(user.data) + : user.data || {} + data[key] = value + await user.$query().update({ data: JSON.stringify(data) }) + } + return true + } + return false + }, setGymBadge: async (_, args, { req }) => { const perms = req.user ? req.user.perms : false if (perms?.gymBadges && req?.user?.id) { diff --git a/server/src/graphql/typeDefs.js b/server/src/graphql/typeDefs.js index 851c22ff0..0eed5b958 100644 --- a/server/src/graphql/typeDefs.js +++ b/server/src/graphql/typeDefs.js @@ -81,7 +81,6 @@ module.exports = gql` webhookName: String ts: Int midnight: Int - onlyAreas: [String] ): [Search] searchQuest( @@ -123,5 +122,6 @@ module.exports = gql` strategy(strategy: String): Boolean checkUsername(username: String): Boolean setGymBadge(gymId: String, badge: Int): Boolean + setExtraFields(key: String, value: String): Boolean } ` diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index 1e14ca7ee..6b474ab7f 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -178,6 +178,7 @@ rootRouter.get('/settings', async (req, res) => { gymValidDataLimit: Date.now() / 1000 - config.api.gymValidDataLimit * 86400, }, + extraUserFields: config.database.settings.extraUserFields, available: { pokemon: [], pokestops: [], gyms: [], nests: [] }, } diff --git a/src/components/layout/FloatingBtn.jsx b/src/components/layout/FloatingBtn.jsx index cfb98f151..4105fce9f 100644 --- a/src/components/layout/FloatingBtn.jsx +++ b/src/components/layout/FloatingBtn.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react' import { Grid, Fab } from '@material-ui/core' import { Menu, - LocationOn, + MyLocation, ZoomIn, ZoomOut, Search, @@ -216,7 +216,7 @@ export default function FloatingButtons({ onClick={() => lc._onClick()} title={t('use_my_location')} > - + diff --git a/src/components/layout/dialogs/UserProfile.jsx b/src/components/layout/dialogs/UserProfile.jsx index 9ea21ae54..eaeed1a10 100644 --- a/src/components/layout/dialogs/UserProfile.jsx +++ b/src/components/layout/dialogs/UserProfile.jsx @@ -15,6 +15,7 @@ import { CardContent, IconButton, Dialog, + TextField, } from '@material-ui/core' import { Edit } from '@material-ui/icons' import { useTranslation } from 'react-i18next' @@ -72,6 +73,7 @@ export default function UserProfile({ setUserProfile, isMobile, isTablet }) {
+ { ) } +const ExtraFields = ({ auth }) => { + const extraUserFields = useStatic((state) => state.extraUserFields) + const setAuth = useStatic((state) => state.setAuth) + + const [setField] = useMutation(Query.user('setExtraFields')) + + return ( + + {extraUserFields.map((field) => { + const locale = localStorage.getItem('i18nextLng') || 'en' + const label = + typeof field === 'string' ? field : field[locale] || field.name + const key = typeof field === 'string' ? field : field.database + if (!key || !label) return null + return ( + + { + setAuth({ + ...auth, + data: { + ...auth.data, + [key]: value, + }, + }) + setField({ + variables: { + key, + value, + }, + }) + }} + /> + + ) + })} + + ) +} + const ProfilePermissions = ({ perms, excludeList, t }) => { const { map: { permImageDir, permArrayImages }, diff --git a/src/hooks/useConfig.js b/src/hooks/useConfig.js index ef0fd4a10..4262be0ec 100644 --- a/src/hooks/useConfig.js +++ b/src/hooks/useConfig.js @@ -38,6 +38,7 @@ export default function useConfig(serverSettings, params) { const setStaticFilters = useStatic((state) => state.setFilters) const setWebhookData = useStatic((state) => state.setWebhookData) const setIsNight = useStatic((state) => state.setIsNight) + const setExtraUserFields = useStatic((state) => state.setExtraUserFields) const localState = JSON.parse(localStorage.getItem('local-state')) @@ -58,14 +59,15 @@ export default function useConfig(serverSettings, params) { } setAuth({ - strategy: serverSettings.user.strategy, - discordId: serverSettings.user.discordId, - telegramId: serverSettings.user.telegramId, - webhookStrategy: serverSettings.user.webhookStrategy, + strategy: serverSettings.user?.strategy || '', + discordId: serverSettings.user?.discordId || '', + telegramId: serverSettings.user?.telegramId || '', + webhookStrategy: serverSettings.user?.webhookStrategy || '', loggedIn: serverSettings.loggedIn, perms: serverSettings.user ? serverSettings.user.perms : {}, methods: serverSettings.authMethods, - username: serverSettings.user.username, + username: serverSettings.user?.username || '', + data: serverSettings.user?.data || {}, }) Sentry.setUser({ username: serverSettings.user.username, @@ -86,6 +88,7 @@ export default function useConfig(serverSettings, params) { setAvailable(serverSettings.available) setMenus(updateObjState(serverSettings.menus, 'menus')) setStaticMenus(serverSettings.menus) + setExtraUserFields(serverSettings.extraUserFields) if (localState?.state?.filters?.pokemon?.standard) { delete localState.state.filters.pokemon.standard diff --git a/src/hooks/useStore.js b/src/hooks/useStore.js index 7e214920a..e59253a8b 100644 --- a/src/hooks/useStore.js +++ b/src/hooks/useStore.js @@ -138,4 +138,6 @@ export const useStatic = create((set) => ({ setFeedback: (feedback) => set({ feedback }), resetFilters: false, setResetFilters: (resetFilters) => set({ resetFilters }), + extraUserFields: [], + setExtraUserFields: (extraUserFields) => set({ extraUserFields }), })) diff --git a/src/services/queries/user.js b/src/services/queries/user.js index 47f71ea4d..c31cd43c7 100644 --- a/src/services/queries/user.js +++ b/src/services/queries/user.js @@ -23,3 +23,9 @@ export const setGymBadge = gql` setGymBadge(gymId: $gymId, badge: $badge) } ` + +export const setExtraFields = gql` + mutation SetExtraFields($key: String, $value: String) { + setExtraFields(key: $key, value: $value) + } +` \ No newline at end of file