Merge branch 'master' into humhub_export_user

This commit is contained in:
einhornimmond 2024-04-04 16:13:16 +02:00
commit a4ab5212c7
54 changed files with 1214 additions and 195 deletions

View File

@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [2.2.1](https://github.com/gradido/gradido/compare/2.2.0...2.2.1)
- fix(other): deployment bugfixes [`#3290`](https://github.com/gradido/gradido/pull/3290)
#### [2.2.0](https://github.com/gradido/gradido/compare/2.1.1...2.2.0)
> 9 February 2024
- chore(release): v2.2.0 [`#3283`](https://github.com/gradido/gradido/pull/3283)
- fix(backend): prevent warning, fix env error [`#3282`](https://github.com/gradido/gradido/pull/3282)
- feat(frontend): update news text [`#3279`](https://github.com/gradido/gradido/pull/3279)
- feat(frontend): use params instead of query for send/identifier route [`#3277`](https://github.com/gradido/gradido/pull/3277)
- fix(other): deployment bugfixes [`#3276`](https://github.com/gradido/gradido/pull/3276)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido",
"main": "index.js",
"author": "Moriz Wahl",
"version": "2.2.0",
"version": "2.2.1",
"license": "Apache-2.0",
"private": false,
"scripts": {

View File

@ -7,7 +7,7 @@ module.exports = {
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {
lines: 84,
lines: 83,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'],

View File

@ -1,6 +1,6 @@
{
"name": "gradido-backend",
"version": "2.2.0",
"version": "2.2.1",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",

View File

@ -117,30 +117,106 @@ export async function userByUuid(uuid: string): Promise<GmsUser[] | string | und
*/
export async function createGmsUser(apiKey: string, user: GmsUser): Promise<boolean> {
if (CONFIG.GMS_ACTIVE) {
const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/')
const service = 'community-user'
const config = {
headers: {
accept: 'application/json',
language: 'en',
timezone: 'UTC',
connection: 'keep-alive',
authorization: apiKey,
},
}
try {
const result = await axios.post(baseUrl.concat(service), user, config)
logger.debug('POST-Response of community-user:', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error in community-user:', result.status, result.statusText)
}
logger.debug('responseData:', result.data.responseData)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
// const gmsUser = JSON.parse(result.data.responseData)
// logger.debug('gmsUser:', gmsUser)
return true
} catch (error: any) {
logger.error('Error in post community-user:', error)
throw new LogError(error.message)
}
} else {
logger.info('GMS-Communication disabled per ConfigKey GMS_ACTIVE=false!')
return false
}
}
export async function updateGmsUser(apiKey: string, user: GmsUser): Promise<boolean> {
if (CONFIG.GMS_ACTIVE) {
const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/')
const service = 'community-user'
const config = {
headers: {
accept: 'application/json',
language: 'en',
timezone: 'UTC',
connection: 'keep-alive',
authorization: apiKey,
},
}
try {
const result = await axios.patch(baseUrl.concat(service), user, config)
logger.debug('PATCH-Response of community-user:', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error in community-user:', result.status, result.statusText)
}
logger.debug('responseData:', result.data.responseData)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
// const gmsUser = JSON.parse(result.data.responseData)
// logger.debug('gmsUser:', gmsUser)
return true
} catch (error: any) {
logger.error('Error in patch community-user:', error)
throw new LogError(error.message)
}
} else {
logger.info('GMS-Communication disabled per ConfigKey GMS_ACTIVE=false!')
return false
}
}
export async function verifyAuthToken(
// apiKey: string,
communityUuid: string,
token: string,
): Promise<string> {
const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/')
const service = 'community-user'
const service = 'verify-auth-token?token='.concat(token).concat('&uuid=').concat(communityUuid)
const config = {
headers: {
accept: 'application/json',
language: 'en',
timezone: 'UTC',
connection: 'keep-alive',
authorization: apiKey,
// authorization: apiKey,
},
}
try {
const result = await axios.post(baseUrl.concat(service), user, config)
logger.debug('POST-Response of community-user:', result)
const result = await axios.get(baseUrl.concat(service), config)
logger.debug('GET-Response of verify-auth-token:', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error in community-user:', result.status, result.statusText)
throw new LogError(
'HTTP Status Error in verify-auth-token:',
result.status,
result.statusText,
)
}
logger.debug('responseData:', result.data.responseData)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
// const gmsUser = JSON.parse(result.data.responseData)
// logger.debug('gmsUser:', gmsUser)
return true
const token: string = result.data.responseData.token
logger.debug('verifyAuthToken=', token)
return token
} catch (error: any) {
logger.error('Error in Get community-user:', error)
logger.error('Error in verifyAuthToken:', error)
throw new LogError(error.message)
}
}

View File

@ -8,6 +8,7 @@ export class GmsUser {
constructor(user: dbUser) {
this.userUuid = user.gradidoID
// this.communityUuid = user.communityUuid
this.language = user.language
this.email = this.getGmsEmail(user)
this.countryCode = this.getGmsCountryCode(user)
this.mobile = this.getGmsPhone(user)

View File

@ -37,6 +37,7 @@ export enum RIGHTS {
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
OPEN_CREATIONS = 'OPEN_CREATIONS',
USER = 'USER',
GMS_USER_PLAYGROUND = 'GMS_USER_PLAYGROUND',
// Moderator
SEARCH_USERS = 'SEARCH_USERS',
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',

View File

@ -29,4 +29,5 @@ export const USER_RIGHTS = [
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
RIGHTS.OPEN_CREATIONS,
RIGHTS.USER,
RIGHTS.GMS_USER_PLAYGROUND,
]

View File

@ -19,7 +19,11 @@ const constants = {
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
<<<<<<< HEAD
EXPECTED: 'v22.2024-03-06',
=======
EXPECTED: 'v22.2024-03-14',
>>>>>>> master
CURRENT: '',
},
}
@ -143,8 +147,11 @@ const federation = {
const gms = {
GMS_ACTIVE: process.env.GMS_ACTIVE === 'true' || false,
GMS_CREATE_USER_THROW_ERRORS: process.env.GMS_CREATE_USER_THROW_ERRORS === 'true' || false,
// koordinates of Illuminz-instance of GMS
GMS_URL: process.env.GMS_HOST ?? 'http://localhost:4044/',
// used as secret postfix attached at the gms community-auth-url endpoint ('/hook/gms/' + 'secret')
GMS_WEBHOOK_SECRET: process.env.GMS_WEBHOOK_SECRET ?? 'secret',
}
const humhub = {

View File

@ -1,6 +1,8 @@
import { IsBoolean, IsInt, IsString } from 'class-validator'
import { IsBoolean, IsEnum, IsInt, IsString } from 'class-validator'
import { ArgsType, Field, InputType, Int } from 'type-graphql'
import { GmsPublishLocationType } from '@enum/GmsPublishLocationType'
import { GmsPublishNameType } from '@enum/GmsPublishNameType'
import { Location } from '@model/Location'
import { isValidLocation } from '@/graphql/validator/Location'
@ -44,19 +46,19 @@ export class UpdateUserInfosArgs {
@IsBoolean()
hideAmountGDT?: boolean
@Field({ nullable: true, defaultValue: true })
@Field({ nullable: true })
@IsBoolean()
gmsAllowed?: boolean
@Field(() => Int, { nullable: true, defaultValue: 0 })
@IsInt()
gmsPublishName?: number | null
@Field(() => GmsPublishNameType, { nullable: true })
@IsEnum(GmsPublishNameType)
gmsPublishName?: GmsPublishNameType | null
@Field(() => Location, { nullable: true })
@isValidLocation()
gmsLocation?: Location | null
@Field(() => Int, { nullable: true, defaultValue: 2 })
@IsInt()
gmsPublishLocation?: number | null
@Field(() => GmsPublishLocationType, { nullable: true })
@IsEnum(GmsPublishLocationType)
gmsPublishLocation?: GmsPublishLocationType | null
}

View File

@ -0,0 +1,10 @@
import { Field, ObjectType } from 'type-graphql'
@ObjectType()
export class GmsUserAuthenticationResult {
@Field(() => String)
url: string
@Field(() => String)
token: string
}

View File

@ -1,6 +1,9 @@
import { User as dbUser } from '@entity/User'
import { ObjectType, Field, Int } from 'type-graphql'
import { GmsPublishLocationType } from '@enum/GmsPublishLocationType'
import { GmsPublishNameType } from '@enum/GmsPublishNameType'
import { KlickTipp } from './KlickTipp'
@ObjectType()
@ -29,6 +32,9 @@ export class User {
this.hasElopage = null
this.hideAmountGDD = user.hideAmountGDD
this.hideAmountGDT = user.hideAmountGDT
this.gmsAllowed = user.gmsAllowed
this.gmsPublishName = user.gmsPublishName
this.gmsPublishLocation = user.gmsPublishLocation
}
}
@ -74,6 +80,15 @@ export class User {
@Field(() => Boolean)
hideAmountGDT: boolean
@Field(() => Boolean)
gmsAllowed: boolean
@Field(() => GmsPublishNameType, { nullable: true })
gmsPublishName: GmsPublishNameType | null
@Field(() => GmsPublishLocationType, { nullable: true })
gmsPublishLocation: GmsPublishLocationType | null
// This is not the users publisherId, but the one of the users who recommend him
@Field(() => Int, { nullable: true })
publisherId: number | null

View File

@ -15,8 +15,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
import { GraphQLError } from 'graphql'
import { v4 as uuidv4 } from 'uuid'
import { GmsPublishLocationType } from '@enum/GmsPublishLocationType'
import { GmsPublishNameType } from '@enum/GmsPublishNameType'
import { cleanDB, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup'
@ -530,13 +528,12 @@ describe('send coins', () => {
describe('send coins via alias', () => {
beforeAll(async () => {
// first set alias to null, because updating alias isn't allowed
await User.update({ alias: 'MeisterBob' }, { alias: () => 'NULL' })
await mutate({
mutation: updateUserInfos,
variables: {
alias: 'bob',
gmsAllowed: true,
gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS,
gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM,
},
})
await mutate({

View File

@ -1258,6 +1258,8 @@ describe('UserResolver', () => {
describe('valid alias', () => {
it('updates the user in DB', async () => {
// first empty alias, because currently updating alias isn't allowed
await User.update({ alias: 'BBB' }, { alias: () => 'NULL' })
await mutate({
mutation: updateUserInfos,
variables: {
@ -1303,8 +1305,10 @@ describe('UserResolver', () => {
mutation: updateUserInfos,
variables: {
gmsAllowed: false,
gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_FIRST_INITIAL,
gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_APPROXIMATE,
gmsPublishName:
GmsPublishNameType[GmsPublishNameType.GMS_PUBLISH_NAME_FIRST_INITIAL],
gmsPublishLocation:
GmsPublishLocationType[GmsPublishLocationType.GMS_LOCATION_TYPE_APPROXIMATE],
},
})
await expect(User.find()).resolves.toEqual([
@ -1326,9 +1330,11 @@ describe('UserResolver', () => {
mutation: updateUserInfos,
variables: {
gmsAllowed: true,
gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS,
gmsPublishName:
GmsPublishNameType[GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS],
gmsLocation: loc,
gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM,
gmsPublishLocation:
GmsPublishLocationType[GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM],
},
})
await expect(User.find()).resolves.toEqual([
@ -2670,13 +2676,12 @@ describe('UserResolver', () => {
mutation: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
// first set alias to null, because updating alias isn't currently allowed
await User.update({ alias: 'BBB' }, { alias: () => 'NULL' })
await mutate({
mutation: updateUserInfos,
variables: {
alias: 'bibi',
gmsAllowed: true,
gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS,
gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM,
},
})
})

View File

@ -19,17 +19,18 @@ import { SearchUsersFilters } from '@arg/SearchUsersFilters'
import { SetUserRoleArgs } from '@arg/SetUserRoleArgs'
import { UnsecureLoginArgs } from '@arg/UnsecureLoginArgs'
import { UpdateUserInfosArgs } from '@arg/UpdateUserInfosArgs'
import { GmsPublishLocationType } from '@enum/GmsPublishLocationType'
import { GmsPublishNameType } from '@enum/GmsPublishNameType'
import { OptInType } from '@enum/OptInType'
import { Order } from '@enum/Order'
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
import { UserContactType } from '@enum/UserContactType'
import { SearchAdminUsersResult } from '@model/AdminUser'
// import { Location } from '@model/Location'
import { GmsUserAuthenticationResult } from '@model/GmsUserAuthenticationResult'
import { User } from '@model/User'
import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
import { updateGmsUser } from '@/apis/gms/GmsClient'
import { GmsUser } from '@/apis/gms/model/GmsUser'
import { subscribe } from '@/apis/KlicktippController'
import { encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
@ -68,7 +69,9 @@ import random from 'random-bigint'
import { randombytes_random } from 'sodium-native'
import { FULL_CREATION_AVAILABLE } from './const/const'
import { authenticateGmsUserPlayground } from './util/authenticateGmsUserPlayground'
import { getHomeCommunity } from './util/communities'
import { compareGmsRelevantUserSettings } from './util/compareGmsRelevantUserSettings'
import { getUserCreations } from './util/creations'
import { findUserByIdentifier } from './util/findUserByIdentifier'
import { findUsers } from './util/findUsers'
@ -375,7 +378,11 @@ export class UserResolver {
await sendUserToGms(dbUser, homeCom)
}
} catch (err) {
logger.error('Error publishing new created user to GMS:', err)
if (CONFIG.GMS_CREATE_USER_THROW_ERRORS) {
throw new LogError('Error publishing new created user to GMS:', err)
} else {
logger.error('Error publishing new created user to GMS:', err)
}
}
}
return new User(dbUser)
@ -395,7 +402,6 @@ export class UserResolver {
logger.warn(`no user found with ${email}`)
return true
}
if (!canEmailResend(user.emailContact.updatedAt || user.emailContact.createdAt)) {
throw new LogError(
`Email already sent less than ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} ago`,
@ -541,8 +547,10 @@ export class UserResolver {
@Authorized([RIGHTS.UPDATE_USER_INFOS])
@Mutation(() => Boolean)
async updateUserInfos(
@Args()
{
@Args() updateUserInfosArgs: UpdateUserInfosArgs,
@Ctx() context: Context,
): Promise<boolean> {
const {
firstName,
lastName,
alias,
@ -555,24 +563,13 @@ export class UserResolver {
gmsPublishName,
gmsLocation,
gmsPublishLocation,
}: UpdateUserInfosArgs,
@Ctx() context: Context,
): Promise<boolean> {
} = updateUserInfosArgs
logger.info(
`updateUserInfos(${firstName}, ${lastName}, ${alias}, ${language}, ***, ***, ${hideAmountGDD}, ${hideAmountGDT}, ${gmsAllowed}, ${gmsPublishName}, ${gmsLocation}, ${gmsPublishLocation})...`,
)
// check default arg settings
if (gmsAllowed === null || gmsAllowed === undefined) {
gmsAllowed = true
}
if (!gmsPublishName) {
gmsPublishName = GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS
}
if (!gmsPublishLocation) {
gmsPublishLocation = GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM
}
const user = getUser(context)
const updateUserInGMS = compareGmsRelevantUserSettings(user, updateUserInfosArgs)
// try {
if (firstName) {
user.firstName = firstName
@ -582,7 +579,8 @@ export class UserResolver {
user.lastName = lastName
}
if (alias && (await validateAlias(alias))) {
// currently alias can only be set, not updated
if (alias && !user.alias && (await validateAlias(alias))) {
user.alias = alias
}
@ -619,13 +617,18 @@ export class UserResolver {
if (hideAmountGDT !== undefined) {
user.hideAmountGDT = hideAmountGDT
}
user.gmsAllowed = gmsAllowed
user.gmsPublishName = gmsPublishName
if (gmsAllowed !== undefined) {
user.gmsAllowed = gmsAllowed
}
if (gmsPublishName !== null && gmsPublishName !== undefined) {
user.gmsPublishName = gmsPublishName
}
if (gmsLocation) {
user.location = Location2Point(gmsLocation)
}
user.gmsPublishLocation = gmsPublishLocation
if (gmsPublishLocation !== null && gmsPublishLocation !== undefined) {
user.gmsPublishLocation = gmsPublishLocation
}
// } catch (err) {
// console.log('error:', err)
// }
@ -649,6 +652,17 @@ export class UserResolver {
logger.info('updateUserInfos() successfully finished...')
await EVENT_USER_INFO_UPDATE(user)
// validate if user settings are changed with relevance to update gms-user
if (CONFIG.GMS_ACTIVE && updateUserInGMS) {
logger.debug(`changed user-settings relevant for gms-user update...`)
const homeCom = await getHomeCommunity()
if (homeCom.gmsApiKey !== null) {
logger.debug(`gms-user update...`, user)
await updateGmsUser(homeCom.gmsApiKey, new GmsUser(user))
logger.debug(`gms-user update successfully.`)
}
}
return true
}
@ -662,6 +676,21 @@ export class UserResolver {
return elopageBuys
}
@Authorized([RIGHTS.GMS_USER_PLAYGROUND])
@Query(() => GmsUserAuthenticationResult)
async authenticateGmsUserSearch(@Ctx() context: Context): Promise<GmsUserAuthenticationResult> {
logger.info(`authUserForGmsUserSearch()...`)
const dbUser = getUser(context)
let result: GmsUserAuthenticationResult
if (context.token) {
result = await authenticateGmsUserPlayground(context.token, dbUser)
logger.info('authUserForGmsUserSearch=', result)
} else {
throw new LogError('authUserForGmsUserSearch without token')
}
return result
}
@Authorized([RIGHTS.SEARCH_ADMIN_USERS])
@Query(() => SearchAdminUsersResult)
async searchAdminUsers(

View File

@ -0,0 +1,17 @@
import { User as DbUser } from '@entity/User'
import { verifyAuthToken } from '@/apis/gms/GmsClient'
import { CONFIG } from '@/config'
import { GmsUserAuthenticationResult } from '@/graphql/model/GmsUserAuthenticationResult'
import { backendLogger as logger } from '@/server/logger'
export async function authenticateGmsUserPlayground(
token: string,
dbUser: DbUser,
): Promise<GmsUserAuthenticationResult> {
const result = new GmsUserAuthenticationResult()
result.url = CONFIG.GMS_URL.concat('/playground')
result.token = await verifyAuthToken(dbUser.communityUuid, token)
logger.info('GmsUserAuthenticationResult:', result)
return result
}

View File

@ -0,0 +1,90 @@
import { Point } from '@dbTools/typeorm'
import { User as DbUser } from '@entity/User'
import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
import { GmsPublishNameType } from '@/graphql/enum/GmsPublishNameType'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { Point2Location } from './Location2Point'
export function compareGmsRelevantUserSettings(
orgUser: DbUser,
updateUserInfosArgs: UpdateUserInfosArgs,
): boolean {
if (!orgUser) {
throw new LogError('comparison without any user is impossible')
}
logger.debug('compareGmsRelevantUserSettings:', orgUser, updateUserInfosArgs)
// nach GMS updaten, wenn alias gesetzt wird oder ist und PublishLevel die alias-Übermittlung erlaubt
if (
updateUserInfosArgs.alias &&
orgUser.alias !== updateUserInfosArgs.alias &&
((updateUserInfosArgs.gmsPublishName &&
updateUserInfosArgs.gmsPublishName.valueOf ===
GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS.valueOf) ||
(!updateUserInfosArgs.gmsPublishName &&
orgUser.gmsPublishName &&
orgUser.gmsPublishName.valueOf ===
GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS.valueOf))
) {
return true
}
if (
(updateUserInfosArgs.firstName && orgUser.firstName !== updateUserInfosArgs.firstName) ||
(updateUserInfosArgs.lastName && orgUser.lastName !== updateUserInfosArgs.lastName)
) {
return true
}
if (
updateUserInfosArgs.gmsAllowed !== undefined &&
updateUserInfosArgs.gmsAllowed &&
orgUser.gmsAllowed !== updateUserInfosArgs.gmsAllowed
) {
return true
}
if (
updateUserInfosArgs.gmsPublishLocation &&
orgUser.gmsPublishLocation !== updateUserInfosArgs.gmsPublishLocation
) {
return true
}
if (
updateUserInfosArgs.gmsPublishName &&
orgUser.gmsPublishName !== updateUserInfosArgs.gmsPublishName
) {
return true
}
if (updateUserInfosArgs.language && orgUser.language !== updateUserInfosArgs.language) {
return true
}
if (
updateUserInfosArgs.gmsLocation &&
orgUser.location === null &&
updateUserInfosArgs.gmsLocation !== null
) {
return true
}
if (
updateUserInfosArgs.gmsLocation &&
orgUser.location !== null &&
updateUserInfosArgs.gmsLocation === null
) {
return true
}
if (
updateUserInfosArgs.gmsLocation &&
orgUser.location !== null &&
updateUserInfosArgs.gmsLocation !== null
) {
const orgLocation = Point2Location(orgUser.location as Point)
const changedLocation = updateUserInfosArgs.gmsLocation
if (
orgLocation.latitude !== changedLocation.latitude ||
orgLocation.longitude !== changedLocation.longitude
) {
return true
}
}
return false
}

View File

@ -3,6 +3,7 @@ import { User as DbUser } from '@entity/User'
import { createGmsUser } from '@/apis/gms/GmsClient'
import { GmsUser } from '@/apis/gms/model/GmsUser'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
@ -22,6 +23,10 @@ export async function sendUserToGms(user: DbUser, homeCom: DbCommunity): Promise
logger.debug('mark user as gms published:', user)
}
} catch (err) {
logger.warn('publishing user fails with ', err)
if (CONFIG.GMS_CREATE_USER_THROW_ERRORS) {
throw new LogError('publishing user fails with ', err)
} else {
logger.warn('publishing user fails with ', err)
}
}
}

View File

@ -39,8 +39,12 @@ export const userFactory = async (
dbUser = await User.findOneOrFail({ where: { id }, relations: ['userRoles'] })
if (user.createdAt || user.deletedAt || user.role) {
if (user.createdAt) dbUser.createdAt = user.createdAt
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
if (user.createdAt) {
dbUser.createdAt = user.createdAt
}
if (user.deletedAt) {
dbUser.deletedAt = user.deletedAt
}
if (user.role && (user.role === RoleNames.ADMIN || user.role === RoleNames.MODERATOR)) {
await setUserRole(dbUser, user.role)
}

View File

@ -35,9 +35,9 @@ export const updateUserInfos = gql`
$hideAmountGDD: Boolean
$hideAmountGDT: Boolean
$gmsAllowed: Boolean
$gmsPublishName: Int
$gmsPublishName: GmsPublishNameType
$gmsLocation: Location
$gmsPublishLocation: Int
$gmsPublishLocation: GmsPublishLocationType
) {
updateUserInfos(
firstName: $firstName

View File

@ -15,6 +15,14 @@ export const verifyLogin = gql`
}
}
`
export const authenticateGmsUserSearch = gql`
query {
authenticateGmsUserSearch {
url
token
}
}
`
export const queryOptIn = gql`
query ($optIn: String!) {

View File

@ -13,6 +13,7 @@ import { schema } from '@/graphql/schema'
import { Connection } from '@/typeorm/connection'
import { checkDBVersion } from '@/typeorm/DBVersion'
import { elopageWebhook } from '@/webhook/elopage'
import { gmsWebhook } from '@/webhook/gms'
import { context as serverContext } from './context'
import { cors } from './cors'
@ -94,6 +95,10 @@ export const createServer = async (
// eslint-disable-next-line @typescript-eslint/no-misused-promises
app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
// GMS Webhook
// eslint-disable-next-line @typescript-eslint/no-misused-promises
app.get('/hook/gms/' + CONFIG.GMS_WEBHOOK_SECRET, gmsWebhook)
// Apollo Server
const apollo = new ApolloServer({
schema: await schema(),

View File

@ -37,6 +37,7 @@ const logPlugin = {
const { logger } = requestContext
const { query, mutation, variables, operationName } = requestContext.request
if (operationName !== 'IntrospectionQuery') {
logger.debug('requestDidStart:', requestContext)
logger.info(`Request:
${mutation || query}variables: ${JSON.stringify(filterVariables(variables), null, 2)}`)
}

View File

@ -0,0 +1,36 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { User as DbUser } from '@entity/User'
import { decode } from '@/auth/JWT'
export const gmsWebhook = async (req: any, res: any): Promise<void> => {
console.log('GMS Hook received', req.query)
const { token } = req.query
if (!token) {
console.log('gmsWebhook: missing token')
res.status(400).json({ message: 'false' })
return
}
const payload = await decode(token)
console.log('gmsWebhook: decoded token=', payload)
if (!payload) {
console.log('gmsWebhook: invalid token')
res.status(400).json({ message: 'false' })
return
}
const user = await DbUser.findOne({ where: { gradidoID: payload.gradidoID } })
if (!user) {
console.log('gmsWebhook: missing user')
res.status(400).json({ message: 'false' })
return
}
console.log('gmsWebhook: authenticate user=', user.gradidoID, user.firstName, user.lastName)
console.log('gmsWebhook: authentication successful')
res.status(200).json({ userUuid: user.gradidoID })
}

View File

@ -1,6 +1,6 @@
{
"name": "gradido-database",
"version": "2.2.0",
"version": "2.2.1",
"description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database",

View File

@ -26,7 +26,7 @@ EMAIL_CODE_REQUEST_TIME=10
# config versions
DATABASE_CONFIG_VERSION=v1.2022-03-18
BACKEND_CONFIG_VERSION=v21.2024-01-06
FRONTEND_CONFIG_VERSION=v5.2024-01-08
FRONTEND_CONFIG_VERSION=v6.2024-02-27
ADMIN_CONFIG_VERSION=v2.2024-01-04
FEDERATION_CONFIG_VERSION=v2.2023-08-24
FEDERATION_DHT_CONFIG_VERSION=v4.2024-01-17
@ -120,12 +120,13 @@ DEFAULT_PUBLISHER_ID=2896
WEBHOOK_ELOPAGE_SECRET=secret
# GMS
#GMS_ACTIVE=true
GMS_ACTIVE=false
# Coordinates of Illuminz test instance
#GMS_URL=http://54.176.169.179:3071
#GMS_URL=http://localhost:4044/
GMS_URL=http://localhost:4044/
# HUMHUB
HUMHUB_ACTIVE=false
#HUMHUB_API_URL=https://community.gradido.net
#HUMHUB_JWT_KEY=
#HUMHUB_JWT_KEY=

View File

@ -1,3 +1,6 @@
# Migration
[Migration from 2.2.0 to 2.2.1](migration/2_2_0-2_2_1/README.md)
# Setup on Hetzner Cloud Server
Suggested OS:
Debian 12

View File

@ -144,7 +144,13 @@ cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
export DB_USER=gradido
# create a new password only if it not already exist
if [ -z "${DB_PASSWORD}" ]; then
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32; echo);
fi
# Check if DB_PASSWORD is still empty, then exit with an error
if [ -z "${DB_PASSWORD}" ]; then
echo "Error: Failed to generate DB_PASSWORD."
exit 1
fi
mysql <<EOFMYSQL
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
@ -156,7 +162,7 @@ EOFMYSQL
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
# Configure backend
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32; echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
# Configure frontend
@ -166,7 +172,7 @@ envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
# Configure dht-node
export FEDERATION_DHT_SEED=$(< /dev/urandom tr -dc a-f0-9 | head -c 32;echo);
export FEDERATION_DHT_SEED=$(< /dev/urandom tr -dc a-f0-9 | head -c 32; echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env
# Configure federation
@ -180,4 +186,4 @@ sudo -u gradido crontab < $LOCAL_SCRIPT_DIR/crontabs.txt
# Start gradido
# Note: on first startup some errors will occur - nothing serious
sudo -u gradido $SCRIPT_PATH/start.sh
sudo -u gradido $SCRIPT_PATH/start.sh $1

View File

@ -0,0 +1,18 @@
## Migrate from Gradido Version 2.2.0 to 2.2.1
### What was wrong
In [hetzner_cloud/install.sh](../../install.sh) there was an error.
$DB_PASSWORD and $JWT_SECRET password generation method don't work with `release-2_2_0` as parameter for install.sh
The Parameter forwarding from branch, `release-2_2_0` in this case to start.sh was also missing.
### What you can do now
You need to only run this [fixInstall.sh](fixInstall.sh) with `release_2_2_1` as parameter
```bash
cd /home/gradido/gradido/deployment/hetzner_cloud/migration/2_2_0-2_2_1
sudo ./fixInstall.sh `release_2_2_1`
```
Basically it will create a new $DB_PASSWORD, $JWT_SECRET and $FEDERATION_DHT_SEED,
update db user with new db password and update .env files in module folders.
Then it will call start.sh with first parameter if ./fixInstall.sh as his first parameter

View File

@ -0,0 +1,66 @@
#!/bin/bash
# check for parameter
if [ -z "$1" ]; then
echo "Usage: Please provide a branch name as the first argument."
exit 1
fi
set -o allexport
SCRIPT_PATH=$(realpath ../../../bare_metal)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
LOCAL_SCRIPT_PATH=$(realpath $0)
LOCAL_SCRIPT_DIR=$(dirname $LOCAL_SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/..
set +o allexport
# Load .env or .env.dist if not present
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$SCRIPT_PATH/.env" ]; then
set -o allexport
source $SCRIPT_PATH/.env
set +o allexport
else
set -o allexport
source $SCRIPT_PATH/.env.dist
set +o allexport
fi
# create db user
export DB_USER=gradido
# create a new password only if it not already exist
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32; echo);
mysql <<EOFMYSQL
ALTER USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
FLUSH PRIVILEGES;
EOFMYSQL
# Configure database
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
# Configure backend
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32; echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
# Configure frontend
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env
# Configure admin
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
# Configure dht-node
export FEDERATION_DHT_SEED=$(< /dev/urandom tr -dc a-f0-9 | head -c 32; echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env
# Configure federation
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/federation/.env.template > $PROJECT_ROOT/federation/.env
# set all created or modified files back to belonging to gradido
chown -R gradido:gradido $PROJECT_ROOT
# Start gradido
# Note: on first startup some errors will occur - nothing serious
sudo -u gradido $SCRIPT_PATH/start.sh $1

View File

@ -1,6 +1,6 @@
{
"name": "gradido-dht-node",
"version": "2.2.0",
"version": "2.2.1",
"description": "Gradido dht-node module",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/",

View File

@ -1,6 +1,6 @@
{
"name": "gradido-dlt-connector",
"version": "2.2.0",
"version": "2.2.1",
"description": "Gradido DLT-Connector",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/",

View File

@ -275,28 +275,45 @@ It contains a map-component of the leaflet library and offers to capture the use
There is no user-specific authentication nor autorization necessary for this dialog as mentioned above.
### GMS user playground dialog (gms efforts)
### GMS user playground dialog
As described in the chapter "User search" above, we need a dialog in GMS to display in a graphical map:
* the location of the user as a red needle, who opens the user search-dialog
* the location of his community as a circle, the invoker belongs to
* the locations of all other users as white needles, belonging to the same community
* the locations of all other users belonging to the same community as white/gray or black needles - depending on the location-type of the user
* circles and needles of all other communities and users, which are nearby the requesting user and community location
There is no user-specific authentication nor autorization necessary for this dialog as mentioned above.
On activation of the menu-entry _user-search_ a technical flow in the background have to prepare the connection between the gradido-system and the gms-component. The following list will describe the necessary steps of all involved components:
Which (filter-)components this playground-dialog should have next to the graphical location map is not clear at the moment. In the first run to display the above mentioned locations of users and communities with circles and needles will be sufficient.
* **gradido-frontend:** user press the menu entry _user search_
* **(1.a) gradido-frontend:** invokes the gradido-backend `authUserForGmsUserSearch`
* **(1.b) gradido-backend:** the method `authUserForGmsUserSearch` reads the context-user of the current request and the uuid of the user's home-community. With these values it prepares the parameters for invokation of the `gms.verifyAuthToken` method. The first parameter is set by the `community-uuid` and the second parameter is a JWT-token with the encrypted `user-uuid` in the payload and signed by the community's privateKey
* **(2.a) gradido-backend:** invokes the `gms.verifyAuthToken` with community-uuid as 1st parameter and JWT-Token as 2nd parameter
* **(2.b) gms-backend:** recieving the request `verifyAuthToken` with the community-uuid and the JWT-token. After searching and verifing the given community-uuid exists in gms, it prepares the invokation of the configured endpoint `community-Auth-Url` of this community by sending the given JWT-token as parameter back to gradido.
* **(3.a) gms-backend:** invokes the endpoint configured in `gms.community-auth-url` with the previous received JWT-token
* **(3.b) gradido-backend:** receives the request at the endpoint "communityAuthUrl" with the previously sent JWT-token. The JWT-token will be verified if the signature is valid and afterwards the payload is decrypted to verify the contained user-data will match with the current context-user of request (1).
* **(4.a) gradido-backend:** in case of valid JWT-token signature and valid payload data the gradido-backend returns TRUE as result of the authentication-request otherwise FALSE.
* **(4.b) gms-backend:** receives the response of request (3) and in case of TRUE the gms-backend prepares to return a complete URI including a _JWT-access-token_ to be used for entering the user-playground. *It will not return gms-data used for the user-playground as the current implementation of the api `verify-auth-token` do.* In case of FALSE prepare returning an authentication-error.
* **(5.a) gms-backend:** returning the complete URI including a _JWT-access-token_ as response of request (2) or an authentication-error
* **(5.b) gradido-backend:** receiving as response of request (2) a complete URI including a _JWT-access-token_ for entering the users-playground on gms or an authentication-error
* **(6.a) gradido-backend:** returning the complete URI including a _JWT-access-token_ as response of request (1) or an expressive error message
* **(6.b) gradido-frontend:** receiving the complete URI including a _JWT-access-token_ after activation of menu-entry "user-search" or an expressive error-message, which will end the _user search_-flow without requesting the gms-frontend (7).
* **(7.a) gradido-frontend:** on opening a new browser-window the gradido-frontend uses the received URI with the _JWT-access-token_ to enter the gms user-playground
* **(7.b) gms-frontend:** receiving the request for the user-playground with an _JWT-access-token_. After verifying the access-token the gms-frontend will read the data for the user given by the access-token and loads all necessary data to render the users playground
The detailed requirements will come up as soon as we get some user expiriences and feedbacks.
The following picture shows the logical flow and interaction between the involved components:
![](./image/usecase-user_search.svg)
The detailed requirements for the playground-dialog will come up as soon as we get some user expiriences and feedbacks.
### GMS Offer Capture dialog (gms efforts)
will come later...
### GMS Need Capture dialog (gms efforts)
will come later...

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1,6 +1,6 @@
{
"name": "gradido-federation",
"version": "2.2.0",
"version": "2.2.1",
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/federation",

View File

@ -21,3 +21,5 @@ META_DESCRIPTION_EN="Gratitude is the currency of the new age. More and more peo
META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem"
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
GMS_ACTIVE=false

View File

@ -24,3 +24,5 @@ META_DESCRIPTION_EN=$META_DESCRIPTION_EN
META_KEYWORDS_DE=$META_KEYWORDS_DE
META_KEYWORDS_EN=$META_KEYWORDS_EN
META_AUTHOR=$META_AUTHOR
GMS_ACTIVE=$GMS_ACTIVE

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "2.2.0",
"version": "2.2.1",
"private": true,
"scripts": {
"start": "node run/server.js",

View File

@ -71,4 +71,16 @@ export default {
.text-color-gdd-yellow {
color: rgb(197 141 56);
}
.dropdown > .dropdown-toggle {
border-radius: 17px;
height: 50px;
text-align: left;
}
.dropdown-toggle::after {
float: right;
top: 50%;
transform: translateY(-50%);
position: relative;
}
</style>

View File

@ -90,20 +90,3 @@ export default {
},
}
</script>
<style>
.community-switch > div,
.community-switch ul.dropdown-menu {
width: 100%;
}
.community-switch > div > button {
border-radius: 17px;
height: 50px;
text-align: left;
}
.community-switch .dropdown-toggle::after {
float: right;
top: 50%;
transform: translateY(-50%);
position: relative;
}
</style>

View File

@ -0,0 +1,8 @@
<template>
<b-button>{{ $t('settings.GMS.location.button') }}</b-button>
</template>
<script>
export default {
name: 'UserGMSLocation',
}
</script>

View File

@ -0,0 +1,79 @@
import { mount } from '@vue/test-utils'
import UserGMSLocationFormat from './UserGMSLocationFormat.vue'
import { toastErrorSpy } from '@test/testSetup'
const mockAPIcall = jest.fn()
const storeCommitMock = jest.fn()
const localVue = global.localVue
describe('UserGMSLocationFormat', () => {
let wrapper
beforeEach(() => {
wrapper = mount(UserGMSLocationFormat, {
mocks: {
$t: (key) => key, // Mocking the translation function
$store: {
state: {
gmsPublishLocation: null,
},
commit: storeCommitMock,
},
$apollo: {
mutate: mockAPIcall,
},
},
localVue,
propsData: {
selectedOption: 'GMS_LOCATION_TYPE_RANDOM',
},
})
})
afterEach(() => {
wrapper.destroy()
})
it('renders the correct dropdown options', () => {
const dropdownItems = wrapper.findAll('.dropdown-item')
expect(dropdownItems.length).toBe(3)
const labels = dropdownItems.wrappers.map((item) => item.text())
expect(labels).toEqual([
'settings.GMS.publish-location.exact',
'settings.GMS.publish-location.approximate',
'settings.GMS.publish-location.random',
])
})
it('updates selected option on click', async () => {
const dropdownItem = wrapper.findAll('.dropdown-item').at(1) // Click the second item
await dropdownItem.trigger('click')
expect(wrapper.emitted().gmsPublishLocation).toBeTruthy()
expect(wrapper.emitted().gmsPublishLocation.length).toBe(1)
expect(wrapper.emitted().gmsPublishLocation[0]).toEqual(['GMS_LOCATION_TYPE_APPROXIMATE'])
})
it('does not update when clicking on already selected option', async () => {
const dropdownItem = wrapper.findAll('.dropdown-item').at(2) // Click the third item (which is already selected)
await dropdownItem.trigger('click')
expect(wrapper.emitted().gmsPublishLocation).toBeFalsy()
})
describe('update with error', () => {
beforeEach(async () => {
mockAPIcall.mockRejectedValue({
message: 'Ouch',
})
const dropdownItem = wrapper.findAll('.dropdown-item').at(1) // Click the second item
await dropdownItem.trigger('click')
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch')
})
})
})

View File

@ -0,0 +1,73 @@
<template>
<div class="user-gms-location-format">
<b-dropdown v-model="selectedOption">
<template slot="button-content">{{ selectedOptionLabel }}</template>
<b-dropdown-item
v-for="option in dropdownOptions"
@click.prevent="update(option)"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
import { updateUserInfos } from '@/graphql/mutations'
export default {
name: 'UserGMSLocationFormat',
data() {
return {
selectedOption: this.$store.state.gmsPublishLocation ?? 'GMS_LOCATION_TYPE_RANDOM',
dropdownOptions: [
{
label: this.$t('settings.GMS.publish-location.exact'),
value: 'GMS_LOCATION_TYPE_EXACT',
},
{
label: this.$t('settings.GMS.publish-location.approximate'),
value: 'GMS_LOCATION_TYPE_APPROXIMATE',
},
{
label: this.$t('settings.GMS.publish-location.random'),
value: 'GMS_LOCATION_TYPE_RANDOM',
},
],
}
},
computed: {
selectedOptionLabel() {
return this.dropdownOptions.find((option) => option.value === this.selectedOption).label
},
},
methods: {
async update(option) {
if (option.value === this.selectedOption) {
return
}
try {
await this.$apollo.mutate({
mutation: updateUserInfos,
variables: {
gmsPublishLocation: option.value,
},
})
this.toastSuccess(this.$t('settings.GMS.publish-location.updated'))
this.selectedOption = option.value
this.$store.commit('gmsPublishLocation', option.value)
this.$emit('gmsPublishLocation', option.value)
} catch (error) {
this.toastError(error.message)
}
},
},
}
</script>
<style>
.user-gms-location-format > .dropdown,
.user-gms-location-format > .dropdown > .dropdown-toggle > ul.dropdown-menu {
width: 100%;
}
</style>

View File

@ -0,0 +1,81 @@
import { mount } from '@vue/test-utils'
import UserGMSNamingFormat from './UserGMSNamingFormat.vue'
import { toastErrorSpy } from '@test/testSetup'
const mockAPIcall = jest.fn()
const storeCommitMock = jest.fn()
const localVue = global.localVue
describe('UserGMSNamingFormat', () => {
let wrapper
beforeEach(() => {
wrapper = mount(UserGMSNamingFormat, {
mocks: {
$t: (key) => key, // Mocking the translation function
$store: {
state: {
gmsPublishName: null,
},
commit: storeCommitMock,
},
$apollo: {
mutate: mockAPIcall,
},
},
localVue,
propsData: {
selectedOption: 'GMS_PUBLISH_NAME_ALIAS_OR_INITALS',
},
})
})
afterEach(() => {
wrapper.destroy()
})
it('renders the correct dropdown options', () => {
const dropdownItems = wrapper.findAll('.dropdown-item')
expect(dropdownItems.length).toBe(5)
const labels = dropdownItems.wrappers.map((item) => item.text())
expect(labels).toEqual([
'settings.GMS.publish-name.alias-or-initials',
'settings.GMS.publish-name.initials',
'settings.GMS.publish-name.first',
'settings.GMS.publish-name.first-initial',
'settings.GMS.publish-name.name-full',
])
})
it('updates selected option on click', async () => {
const dropdownItem = wrapper.findAll('.dropdown-item').at(3) // Click the fourth item
await dropdownItem.trigger('click')
expect(wrapper.emitted().gmsPublishName).toBeTruthy()
expect(wrapper.emitted().gmsPublishName.length).toBe(1)
expect(wrapper.emitted().gmsPublishName[0]).toEqual(['GMS_PUBLISH_NAME_FIRST_INITIAL'])
})
it('does not update when clicking on already selected option', async () => {
const dropdownItem = wrapper.findAll('.dropdown-item').at(0) // Click the first item (which is already selected)
await dropdownItem.trigger('click')
expect(wrapper.emitted().gmsPublishName).toBeFalsy()
})
describe('update with error', () => {
beforeEach(async () => {
mockAPIcall.mockRejectedValue({
message: 'Ouch',
})
const dropdownItem = wrapper.findAll('.dropdown-item').at(2) // Click the third item
await dropdownItem.trigger('click')
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch')
})
})
})

View File

@ -0,0 +1,87 @@
<template>
<div class="user-gms-naming-format">
<b-dropdown v-model="selectedOption">
<template slot="button-content">{{ selectedOptionLabel }}</template>
<b-dropdown-item
v-for="option in dropdownOptions"
@click.prevent="update(option)"
:key="option.value"
:value="option.value"
:title="option.title"
>
{{ option.label }}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
import { updateUserInfos } from '@/graphql/mutations'
export default {
name: 'UserGMSNamingFormat',
data() {
return {
selectedOption: this.$store.state.gmsPublishName ?? 'GMS_PUBLISH_NAME_ALIAS_OR_INITALS',
dropdownOptions: [
{
label: this.$t('settings.GMS.publish-name.alias-or-initials'),
title: this.$t('settings.GMS.publish-name.alias-or-initials-tooltip'),
value: 'GMS_PUBLISH_NAME_ALIAS_OR_INITALS',
},
{
label: this.$t('settings.GMS.publish-name.initials'),
title: this.$t('settings.GMS.publish-name.initials-tooltip'),
value: 'GMS_PUBLISH_NAME_INITIALS',
},
{
label: this.$t('settings.GMS.publish-name.first'),
title: this.$t('settings.GMS.publish-name.first-tooltip'),
value: 'GMS_PUBLISH_NAME_FIRST',
},
{
label: this.$t('settings.GMS.publish-name.first-initial'),
title: this.$t('settings.GMS.publish-name.first-initial-tooltip'),
value: 'GMS_PUBLISH_NAME_FIRST_INITIAL',
},
{
label: this.$t('settings.GMS.publish-name.name-full'),
title: this.$t('settings.GMS.publish-name.name-full-tooltip'),
value: 'GMS_PUBLISH_NAME_FULL',
},
],
}
},
computed: {
selectedOptionLabel() {
return this.dropdownOptions.find((option) => option.value === this.selectedOption).label
},
},
methods: {
async update(option) {
if (option.value === this.selectedOption) {
return
}
try {
await this.$apollo.mutate({
mutation: updateUserInfos,
variables: {
gmsPublishName: option.value,
},
})
this.toastSuccess(this.$t('settings.GMS.publish-name.updated'))
this.selectedOption = option.value
this.$store.commit('gmsPublishName', option.value)
this.$emit('gmsPublishName', option.value)
} catch (error) {
this.toastError(error.message)
}
},
},
}
</script>
<style>
.user-gms-naming-format > .dropdown,
.user-gms-naming-format > .dropdown > .dropdown-toggle > ul.dropdown-menu {
width: 100%;
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div class="form-user-gms-switch">
<b-form-checkbox
test="BFormCheckbox"
v-model="gmsAllowed"
name="check-button"
switch
@change="onChange"
></b-form-checkbox>
</div>
</template>
<script>
import { updateUserInfos } from '@/graphql/mutations'
export default {
name: 'UserGMSSwitch',
data() {
return {
gmsAllowed: this.$store.state.gmsAllowed,
}
},
methods: {
async onChange() {
this.$apollo
.mutate({
mutation: updateUserInfos,
variables: {
gmsAllowed: this.gmsAllowed,
},
})
.then(() => {
this.$store.commit('gmsAllowed', this.gmsAllowed)
this.$emit('gmsAllowed', this.gmsAllowed)
this.toastSuccess(
this.gmsAllowed ? this.$t('settings.GMS.enabled') : this.$t('settings.GMS.disabled'),
)
})
.catch((error) => {
this.gmsAllowed = this.$store.state.gmsAllowed
this.toastError(error.message)
})
},
},
}
</script>

View File

@ -8,7 +8,7 @@ const constants = {
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v5.2024-01-08',
EXPECTED: 'v6.2024-02-27',
CURRENT: '',
},
}
@ -20,6 +20,10 @@ const version = {
BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7),
}
const features = {
GMS_ACTIVE: process.env.GMS_ACTIVE ?? false,
}
const environment = {
NODE_ENV: process.env.NODE_ENV,
DEBUG: process.env.NODE_ENV !== 'production' ?? false,
@ -81,6 +85,7 @@ if (
const CONFIG = {
...constants,
...version,
...features,
...environment,
...endpoints,
...community,

View File

@ -35,9 +35,9 @@ export const updateUserInfos = gql`
$hideAmountGDD: Boolean
$hideAmountGDT: Boolean
$gmsAllowed: Boolean
$gmsPublishName: Int
$gmsPublishName: GmsPublishNameType
$gmsLocation: Location
$gmsPublishLocation: Int
$gmsPublishLocation: GmsPublishLocationType
) {
updateUserInfos(
firstName: $firstName
@ -172,6 +172,9 @@ export const login = gql`
klickTipp {
newsletterState
}
gmsAllowed
gmsPublishName
gmsPublishLocation
hasElopage
publisherId
roles

View File

@ -5,8 +5,10 @@
"1000thanks": "1000 Dank, weil du bei uns bist!",
"125": "125%",
"85": "85%",
"ExternServices": "Verknüpfte Dienste",
"GDD": "GDD",
"GDT": "GDT",
"GMS": "Gradido Karte",
"PersonalDetails": "Persönliche Angaben",
"advanced-calculation": "Vorausberechnung",
"asterisks": "****",
@ -290,6 +292,36 @@
},
"settings": {
"emailInfo": "Kann aktuell noch nicht geändert werden.",
"GMS": {
"disabled": "Daten werden nicht nach GMS exportiert",
"enabled": "Daten werden nach GMS exportiert",
"location": {
"label": "Positionsbestimmung",
"button": "Klick mich!"
},
"location-format": "Positionstyp",
"naming-format": "Namensformat im GMS",
"publish-location": {
"exact": "Genaue Position",
"approximate": "Ungefähre Position",
"random": "Zufallsposition",
"updated": "Positionstyp für GMS aktualisiert"
},
"publish-name": {
"alias-or-initials": "Benutzername oder Initialen",
"alias-or-initials-tooltip": "Benutzername, falls vorhanden, oder die Initialen von Vorname und Nachname",
"first": "Vorname",
"first-tooltip": "Nur der Vornamen",
"first-initial": "Vorname und Initiale",
"first-initial-tooltip": "Vornamen plus Anfangsbuchstabe des Nachnamens",
"initials": "Initialen",
"initials-tooltip": "Initialen von Vor- und Nachname unabhängig von der Existenz des Benutzernamens",
"name-full": "Ganzer Name",
"name-full-tooltip": "Vollständiger Name: Vorname plus Nachname",
"updated": "Namensformat für GMS aktualisiert"
},
"switch": "Erlaubnis Daten nach GMS zu exportieren."
},
"hideAmountGDD": "Dein GDD Betrag ist versteckt.",
"hideAmountGDT": "Dein GDT Betrag ist versteckt.",
"info": "Transaktionen können nun per Benutzername oder E-Mail-Adresse getätigt werden.",

View File

@ -5,8 +5,10 @@
"1000thanks": "1000 thanks for being with us!",
"125": "125%",
"85": "85%",
"ExternServices": "Extern Services",
"GDD": "GDD",
"GDT": "GDT",
"GMS": "Gradido Map",
"PersonalDetails": "Personal details",
"advanced-calculation": "Advanced calculation",
"asterisks": "****",
@ -290,6 +292,36 @@
},
"settings": {
"emailInfo": "Cannot be changed at this time.",
"GMS": {
"disabled": "Data not exported to GMS",
"enabled": "Data exported to GMS",
"location": {
"label": "pinpoint location",
"button": "click me!"
},
"location-format": "location type",
"naming-format": "Format of name in GMS",
"publish-location": {
"exact": "exact position",
"approximate": "approximate position",
"random": "random position",
"updated": "format of location for GMS updated"
},
"publish-name": {
"alias-or-initials": "Username or initials",
"alias-or-initials-tooltip": "username if exists or Initials of firstname and lastname",
"first": "firstname",
"first-tooltip": "the firstname only",
"first-initial": "firstname and initial",
"first-initial-tooltip": "firstname plus initial of lastname",
"initials": "Initials of firstname and lastname independent if username exists",
"initials-tooltip": "Initials of firstname and lastname independent if username exists",
"name-full": "fullname",
"name-full-tooltip": "fullname: firstname plus lastname",
"updated": "format of name for GMS updated"
},
"switch": "Allow data export to GMS"
},
"hideAmountGDD": "Your GDD amount is hidden.",
"hideAmountGDT": "Your GDT amount is hidden.",
"info": "Transactions can now be made by username or email address.",

View File

@ -1,81 +1,129 @@
<template>
<div class="card bg-white gradido-border-radius appBoxShadow p-4 mt--3">
<div class="h2">{{ $t('PersonalDetails') }}</div>
<div class="my-4 text-small">
{{ $t('settings.info') }}
</div>
<b-row>
<b-col cols="12" md="6" lg="6">
<user-name />
</b-col>
<b-col cols="12" md="6" lg="6">
<b-form-group :label="$t('form.email')" :description="$t('settings.emailInfo')">
<b-form-input v-model="email" readonly></b-form-input>
</b-form-group>
</b-col>
</b-row>
<hr />
<b-form>
<b-row class="mt-3">
<b-col cols="12" md="6" lg="6">
<label>{{ $t('form.firstname') }}</label>
<b-form-input
v-model="firstName"
:placeholder="$t('settings.name.enterFirstname')"
data-test="firstname"
trim
></b-form-input>
</b-col>
<b-col cols="12" md="6" lg="6">
<label>{{ $t('form.lastname') }}</label>
<b-form-input
v-model="lastName"
:placeholder="$t('settings.name.enterLastname')"
data-test="lastname"
trim
></b-form-input>
</b-col>
</b-row>
<div v-if="!isDisabled" class="mt-4 pt-4 text-center">
<b-button
type="submit"
variant="primary"
@click.prevent="onSubmit"
data-test="submit-userdata"
>
{{ $t('form.save') }}
</b-button>
</div>
</b-form>
<hr />
<b-row>
<b-col cols="12" md="6" lg="6">{{ $t('language') }}</b-col>
<b-col cols="12" md="6" lg="6" class="text-right">
<user-language />
</b-col>
</b-row>
<hr />
<div class="mt-5">{{ $t('form.password') }}</div>
<user-password />
<hr />
<b-row class="mb-5">
<b-col cols="12" md="6" lg="6">
{{ $t('settings.newsletter.newsletter') }}
<div class="text-small">
{{
newsletterState
? $t('settings.newsletter.newsletterTrue')
: $t('settings.newsletter.newsletterFalse')
}}
<b-tabs content-class="mt-3">
<b-tab :title="$t('PersonalDetails')" active>
<div class="h2">{{ $t('PersonalDetails') }}</div>
<div class="my-4 text-small">
{{ $t('settings.info') }}
</div>
</b-col>
<b-col cols="12" md="6" lg="6" class="text-right">
<user-newsletter />
</b-col>
</b-row>
<b-row>
<b-col cols="12" md="6" lg="6">
<user-name />
</b-col>
<b-col cols="12" md="6" lg="6">
<b-form-group :label="$t('form.email')" :description="$t('settings.emailInfo')">
<b-form-input v-model="email" readonly></b-form-input>
</b-form-group>
</b-col>
</b-row>
<hr />
<b-form>
<b-row class="mt-3">
<b-col cols="12" md="6" lg="6">
<label>{{ $t('form.firstname') }}</label>
<b-form-input
v-model="firstName"
:placeholder="$t('settings.name.enterFirstname')"
data-test="firstname"
trim
></b-form-input>
</b-col>
<b-col cols="12" md="6" lg="6">
<label>{{ $t('form.lastname') }}</label>
<b-form-input
v-model="lastName"
:placeholder="$t('settings.name.enterLastname')"
data-test="lastname"
trim
></b-form-input>
</b-col>
</b-row>
<div v-if="!isDisabled" class="mt-4 pt-4 text-center">
<b-button
type="submit"
variant="primary"
@click.prevent="onSubmit"
data-test="submit-userdata"
>
{{ $t('form.save') }}
</b-button>
</div>
</b-form>
<hr />
<b-row>
<b-col cols="12" md="6" lg="6">{{ $t('language') }}</b-col>
<b-col cols="12" md="6" lg="6" class="text-right">
<user-language />
</b-col>
</b-row>
<hr />
<div class="mt-5">{{ $t('form.password') }}</div>
<user-password />
<hr />
<b-row class="mb-5">
<b-col cols="12" md="6" lg="6">
{{ $t('settings.newsletter.newsletter') }}
<div class="text-small">
{{
newsletterState
? $t('settings.newsletter.newsletterTrue')
: $t('settings.newsletter.newsletterFalse')
}}
</div>
</b-col>
<b-col cols="12" md="6" lg="6" class="text-right">
<user-newsletter />
</b-col>
</b-row>
</b-tab>
<div v-if="isGMS">
<b-tab :title="$t('ExternServices')">
<div class="h2">{{ $t('ExternServices') }}</div>
<div class="h3">{{ $t('GMS') }}</div>
<b-row class="mb-3">
<b-col cols="12" md="6" lg="6">
{{ $t('settings.GMS.switch') }}
<div class="text-small">
{{ gmsAllowed ? $t('settings.GMS.enabled') : $t('settings.GMS.disabled') }}
</div>
</b-col>
<b-col cols="12" md="6" lg="6" class="text-right">
<user-g-m-s-switch @gmsAllowed="gmsStateSwitch" />
</b-col>
</b-row>
<div v-if="gmsAllowed">
<b-row class="mb-4">
<b-col cols="12" md="6" lg="6">
{{ $t('settings.GMS.naming-format') }}
</b-col>
<b-col cols="12" md="6" lg="6">
<user-g-m-s-naming-format />
</b-col>
</b-row>
<b-row class="mb-4">
<b-col cols="12" md="6" lg="6">
{{ $t('settings.GMS.location-format') }}
</b-col>
<b-col cols="12" md="6" lg="6">
<user-g-m-s-location-format />
</b-col>
</b-row>
<b-row class="mb-5">
<b-col cols="12" md="6" lg="6">
{{ $t('settings.GMS.location.label') }}
</b-col>
<b-col cols="12" md="6" lg="6">
<user-g-m-s-location />
</b-col>
</b-row>
</div>
</b-tab>
</div>
</b-tabs>
<!-- TODO<b-row>
<b-col cols="12" md="6" lg="6">{{ $t('settings.darkMode') }}</b-col>
<b-col cols="12" md="6" lg="6" class="text-right">
@ -85,15 +133,24 @@
</div>
</template>
<script>
import UserGMSSwitch from '@/components/UserSettings/UserGMSSwitch'
import UserGMSNamingFormat from '@/components/UserSettings/UserGMSNamingFormat'
import UserGMSLocationFormat from '@/components/UserSettings/UserGMSLocationFormat'
import UserGMSLocation from '@/components/UserSettings/UserGMSLocation'
import UserName from '@/components/UserSettings/UserName.vue'
import UserPassword from '@/components/UserSettings/UserPassword'
import UserLanguage from '@/components/LanguageSwitch2.vue'
import UserNewsletter from '@/components/UserSettings/UserNewsletter.vue'
import { updateUserInfos } from '@/graphql/mutations'
import CONFIG from '../config'
export default {
name: 'Profile',
components: {
UserGMSSwitch,
UserGMSNamingFormat,
UserGMSLocationFormat,
UserGMSLocation,
UserName,
UserPassword,
UserLanguage,
@ -106,7 +163,7 @@ export default {
data() {
const { state } = this.$store
const { darkMode, firstName, lastName, email, newsletterState } = state
const { darkMode, firstName, lastName, email, newsletterState, gmsAllowed } = state
return {
darkMode,
@ -115,6 +172,7 @@ export default {
lastName,
email,
newsletterState,
gmsAllowed,
mutation: '',
variables: {},
}
@ -125,6 +183,9 @@ export default {
const { firstName, lastName } = this.$store.state
return firstName === this.firstName && lastName === this.lastName
},
isGMS() {
return CONFIG.GMS_ACTIVE
},
},
// TODO: watch: {
// darkMode(val) {
@ -150,6 +211,9 @@ export default {
this.toastSuccess(this.$t('settings.name.change-success'))
} catch (error) {}
},
gmsStateSwitch(eventData) {
this.gmsAllowed = eventData
},
},
}
</script>

View File

@ -35,6 +35,15 @@ export const mutations = {
newsletterState: (state, newsletterState) => {
state.newsletterState = newsletterState
},
gmsAllowed: (state, gmsAllowed) => {
state.gmsAllowed = gmsAllowed
},
gmsPublishName: (state, gmsPublishName) => {
state.gmsPublishName = gmsPublishName
},
gmsPublishLocation: (state, gmsPublishLocation) => {
state.gmsPublishLocation = gmsPublishLocation
},
publisherId: (state, publisherId) => {
let pubId = parseInt(publisherId)
if (isNaN(pubId)) pubId = null
@ -71,6 +80,9 @@ export const actions = {
commit('firstName', data.firstName)
commit('lastName', data.lastName)
commit('newsletterState', data.klickTipp.newsletterState)
commit('gmsAllowed', data.gmsAllowed)
commit('gmsPublishName', data.gmsPublishName)
commit('gmsPublishLocation', data.gmsPublishLocation)
commit('hasElopage', data.hasElopage)
commit('publisherId', data.publisherId)
commit('roles', data.roles)
@ -85,6 +97,9 @@ export const actions = {
commit('firstName', '')
commit('lastName', '')
commit('newsletterState', null)
commit('gmsAllowed', null)
commit('gmsPublishName', null)
commit('gmsPublishLocation', null)
commit('hasElopage', false)
commit('publisherId', null)
commit('roles', null)
@ -117,6 +132,9 @@ try {
tokenTime: null,
roles: [],
newsletterState: null,
gmsAllowed: null,
gmsPublishName: null,
gmsPublishLocation: null,
hasElopage: false,
publisherId: null,
hideAmountGDD: null,
@ -126,7 +144,7 @@ try {
redirectPath: '/overview',
},
getters: {},
// Syncronous mutation of the state
// Synchronous mutation of the state
mutations,
actions,
})

View File

@ -28,6 +28,9 @@ const {
lastName,
username,
newsletterState,
gmsAllowed,
gmsPublishName,
gmsPublishLocation,
publisherId,
roles,
hasElopage,
@ -122,6 +125,30 @@ describe('Vuex store', () => {
})
})
describe('gmsAllowed', () => {
it('sets the state of gmsAllowed', () => {
const state = { gmsAllowed: null }
gmsAllowed(state, true)
expect(state.gmsAllowed).toEqual(true)
})
})
describe('gmsPublishName', () => {
it('sets gmsPublishName', () => {
const state = { gmsPublishName: null }
gmsPublishName(state, 'GMS_PUBLISH_NAME_INITIALS')
expect(state.gmsPublishName).toEqual('GMS_PUBLISH_NAME_INITIALS')
})
})
describe('gmsPublishLocation', () => {
it('sets gmsPublishLocation', () => {
const state = { gmsPublishLocation: null }
gmsPublishLocation(state, 'GMS_LOCATION_TYPE_APPROXIMATE')
expect(state.gmsPublishLocation).toEqual('GMS_LOCATION_TYPE_APPROXIMATE')
})
})
describe('publisherId', () => {
it('sets the state of publisherId', () => {
const state = {}
@ -190,6 +217,9 @@ describe('Vuex store', () => {
klickTipp: {
newsletterState: true,
},
gmsAllowed: true,
gmsPublishName: 'GMS_PUBLISH_NAME_FULL',
gmsPublishLocation: 'GMS_LOCATION_TYPE_EXACT',
hasElopage: false,
publisherId: 1234,
roles: ['admin'],
@ -197,9 +227,9 @@ describe('Vuex store', () => {
hideAmountGDT: true,
}
it('calls eleven commits', () => {
it('calls fifteen commits', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenCalledTimes(12)
expect(commit).toHaveBeenCalledTimes(15)
})
it('commits gradidoID', () => {
@ -232,29 +262,44 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenNthCalledWith(6, 'newsletterState', true)
})
it('commits gmsAllowed', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(7, 'gmsAllowed', true)
})
it('commits gmsPublishName', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(8, 'gmsPublishName', 'GMS_PUBLISH_NAME_FULL')
})
it('commits gmsPublishLocation', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(9, 'gmsPublishLocation', 'GMS_LOCATION_TYPE_EXACT')
})
it('commits hasElopage', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(7, 'hasElopage', false)
expect(commit).toHaveBeenNthCalledWith(10, 'hasElopage', false)
})
it('commits publisherId', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', 1234)
expect(commit).toHaveBeenNthCalledWith(11, 'publisherId', 1234)
})
it('commits roles', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(9, 'roles', ['admin'])
expect(commit).toHaveBeenNthCalledWith(12, 'roles', ['admin'])
})
it('commits hideAmountGDD', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(10, 'hideAmountGDD', false)
expect(commit).toHaveBeenNthCalledWith(13, 'hideAmountGDD', false)
})
it('commits hideAmountGDT', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(11, 'hideAmountGDT', true)
expect(commit).toHaveBeenNthCalledWith(14, 'hideAmountGDT', true)
})
})
@ -262,9 +307,9 @@ describe('Vuex store', () => {
const commit = jest.fn()
const state = {}
it('calls twelve commits', () => {
it('calls seventeen commits', () => {
logout({ commit, state })
expect(commit).toHaveBeenCalledTimes(14)
expect(commit).toHaveBeenCalledTimes(17)
})
it('commits token', () => {
@ -297,34 +342,49 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenNthCalledWith(6, 'newsletterState', null)
})
it('commits gmsAllowed', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(7, 'gmsAllowed', null)
})
it('commits gmsPublishName', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(8, 'gmsPublishName', null)
})
it('commits gmsPublishLocation', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(9, 'gmsPublishLocation', null)
})
it('commits hasElopage', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(7, 'hasElopage', false)
expect(commit).toHaveBeenNthCalledWith(10, 'hasElopage', false)
})
it('commits publisherId', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', null)
expect(commit).toHaveBeenNthCalledWith(11, 'publisherId', null)
})
it('commits roles', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(9, 'roles', null)
expect(commit).toHaveBeenNthCalledWith(12, 'roles', null)
})
it('commits hideAmountGDD', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(10, 'hideAmountGDD', false)
expect(commit).toHaveBeenNthCalledWith(13, 'hideAmountGDD', false)
})
it('commits hideAmountGDT', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(11, 'hideAmountGDT', true)
expect(commit).toHaveBeenNthCalledWith(14, 'hideAmountGDT', true)
})
it('commits email', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(12, 'email', '')
expect(commit).toHaveBeenNthCalledWith(15, 'email', '')
})
// how to get this working?

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "2.2.0",
"version": "2.2.1",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",