diff --git a/backend/src/graphql/arg/UpdateUserInfosArgs.ts b/backend/src/graphql/arg/UpdateUserInfosArgs.ts index ab1263eee..40e63f488 100644 --- a/backend/src/graphql/arg/UpdateUserInfosArgs.ts +++ b/backend/src/graphql/arg/UpdateUserInfosArgs.ts @@ -1,8 +1,13 @@ -import { IsBoolean, IsInt, IsObject, IsString } from 'class-validator' -import { ArgsType, Field, Int } from 'type-graphql' +import { IsBoolean, IsInt, IsString } from 'class-validator' +import { ArgsType, Field, InputType, Int } from 'type-graphql' import { Location } from '@model/Location' +import { isValidLocation } from '../validator/Location' + +// import { isValidLocation } from '../validator/Location' + +@InputType() @ArgsType() export class UpdateUserInfosArgs { @Field({ nullable: true }) @@ -50,7 +55,7 @@ export class UpdateUserInfosArgs { gmsPublishName: number @Field(() => Location, { nullable: true }) - @IsObject() + @isValidLocation() gmsLocation?: Location | null @Field(() => Int, { nullable: false }) diff --git a/backend/src/graphql/model/Location.ts b/backend/src/graphql/model/Location.ts index 281ee9ff4..2b4c720f4 100644 --- a/backend/src/graphql/model/Location.ts +++ b/backend/src/graphql/model/Location.ts @@ -1,31 +1,10 @@ -import { Point } from '@dbTools/typeorm' -import { ArgsType, Field, InputType, Int } from 'type-graphql' +import { ArgsType, Field, Int } from 'type-graphql' -@InputType() @ArgsType() -// @ObjectType() export class Location { - constructor(lon: number, lat: number) { - this.longitude = lon - this.latitude = lat - } - @Field(() => Int) longitude: number @Field(() => Int) latitude: number - - // point is no Field and not part of the graphql type - private point: Point - - public getPoint(): Point { - const pointStr = '{ "type": "Point", "coordinates": [' - .concat(this.longitude.toString()) - .concat(', ') - .concat(this.latitude.toString()) - .concat('] }') - this.point = JSON.parse(pointStr) as Point - return this.point - } } diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 7a81b7017..f58cb53b0 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -23,6 +23,7 @@ import { PasswordEncryptionType } from '@enum/PasswordEncryptionType' import { RoleNames } from '@enum/RoleNames' import { UserContactType } from '@enum/UserContactType' import { ContributionLink } from '@model/ContributionLink' +import { Location } from '@model/Location' import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers' import { logger, i18n as localization } from '@test/testSetup' @@ -1237,6 +1238,9 @@ describe('UserResolver', () => { firstName: 'Benjamin', lastName: 'Blümchen', language: 'en', + gmsAllowed: true, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM, }), ]) }) @@ -1275,6 +1279,93 @@ describe('UserResolver', () => { await expect(User.find()).resolves.toEqual([ expect.objectContaining({ alias: 'bibi_Bloxberg', + gmsAllowed: true, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM, + }), + ]) + }) + }) + }) + + describe('gms attributes', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('default settings', () => { + it('updates the user in DB', async () => { + await mutate({ + mutation: updateUserInfos, + variables: { + gmsAllowed: true, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM, + }, + }) + await expect(User.find()).resolves.toEqual([ + expect.objectContaining({ + gmsAllowed: true, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM, + }), + ]) + }) + }) + + describe('individual settings', () => { + it('updates the user in DB', async () => { + await mutate({ + mutation: updateUserInfos, + variables: { + gmsAllowed: false, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_FIRST_INITIAL, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_APPROXIMATE, + }, + }) + await expect(User.find()).resolves.toEqual([ + expect.objectContaining({ + gmsAllowed: false, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_FIRST_INITIAL, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_APPROXIMATE, + }), + ]) + }) + }) + + describe.only('with gms location', () => { + const loc = new Location() + loc.longitude = 9.573224 + loc.latitude = 49.679437 + console.log('with gms location:', loc) + it('updates the user in DB', async () => { + const usr = await User.find() + console.log('usr=', usr) + await mutate({ + mutation: updateUserInfos, + variables: { + /* + firstName: usr[0].firstName, + lastName: usr[0].lastName, + alias: usr[0].alias, + language: usr[0].language, + password: usr[0].password, + passwordNew: usr[0].password, + hideAmountGDD: usr[0].hideAmountGDD, + hideAmountGDT: usr[0].hideAmountGDT, + */ + gmsAllowed: true, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS, + gmsLocation: loc, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM, + }, + }) + await expect(User.find()).resolves.toEqual([ + expect.objectContaining({ + gmsAllowed: true, + gmsPublishName: GmsPublishNameType.GMS_PUBLISH_NAME_ALIAS_OR_INITALS, + location: loc, + gmsPublishLocation: GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM, }), ]) }) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index fa00a4d57..757ddd5d3 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -24,6 +24,7 @@ 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 { User } from '@model/User' import { UserAdmin, SearchUsersResult } from '@model/UserAdmin' @@ -73,6 +74,7 @@ import { getKlicktippState } from './util/getKlicktippState' import { setUserRole, deleteUserRole } from './util/modifyUserRole' import { sendUserToGms } from './util/sendUserToGms' import { validateAlias } from './util/validateAlias' +import { Location2Point } from './util/Location2Point' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' @@ -554,75 +556,88 @@ export class UserResolver { }: UpdateUserInfosArgs, @Ctx() context: Context, ): Promise { + console.log( + `updateUserInfos(${firstName}, ${lastName}, ${alias}, ${language}, ${password}, ${passwordNew}, ${hideAmountGDD}, ${hideAmountGDT}, ${gmsAllowed}, ${gmsPublishName}, ${gmsLocation}, ${gmsPublishLocation})`, + ) logger.info( - `updateUserInfos(${firstName}, ${lastName}, ${language}, ***, ***, ${gmsAllowed}, ${gmsPublishName}, ${gmsLocation}, ${gmsPublishLocation})...`, + `updateUserInfos(${firstName}, ${lastName}, ${alias}, ${language}, ***, ***, ${hideAmountGDD}, ${hideAmountGDT}, ${gmsAllowed}, ${gmsPublishName}, ${gmsLocation}, ${gmsPublishLocation})...`, ) const user = getUser(context) - - if (firstName) { - user.firstName = firstName - } - - if (lastName) { - user.lastName = lastName - } - - if (alias && (await validateAlias(alias))) { - user.alias = alias - } - - if (language) { - if (!isLanguage(language)) { - throw new LogError('Given language is not a valid language', language) - } - user.language = language - i18n.setLocale(language) - } - - if (password && passwordNew) { - // Validate Password - if (!isValidPassword(passwordNew)) { - throw new LogError( - 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!', - ) + console.log('getUser:', user) + try { + if (firstName) { + user.firstName = firstName } - if (!verifyPassword(user, password)) { - throw new LogError(`Old password is invalid`) + if (lastName) { + user.lastName = lastName } - // Save new password hash and newly encrypted private key - user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID - user.password = encryptPassword(user, passwordNew) - } + if (alias && (await validateAlias(alias))) { + user.alias = alias + } - // Save hideAmountGDD value - if (hideAmountGDD !== undefined) { - user.hideAmountGDD = hideAmountGDD - } - // Save hideAmountGDT value - if (hideAmountGDT !== undefined) { - user.hideAmountGDT = hideAmountGDT - } + if (language) { + if (!isLanguage(language)) { + throw new LogError('Given language is not a valid language', language) + } + user.language = language + i18n.setLocale(language) + } - user.gmsAllowed = gmsAllowed - user.gmsPublishName = gmsPublishName - if (gmsLocation) { - user.location = gmsLocation.getPoint() - } - user.gmsPublishLocation = gmsPublishLocation + if (password && passwordNew) { + // Validate Password + if (!isValidPassword(passwordNew)) { + throw new LogError( + 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!', + ) + } + if (!verifyPassword(user, password)) { + throw new LogError(`Old password is invalid`) + } + + // Save new password hash and newly encrypted private key + user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID + user.password = encryptPassword(user, passwordNew) + } + + // Save hideAmountGDD value + if (hideAmountGDD !== undefined) { + user.hideAmountGDD = hideAmountGDD + } + // Save hideAmountGDT value + if (hideAmountGDT !== undefined) { + user.hideAmountGDT = hideAmountGDT + } + + console.log('gmsAllowed:', user.gmsAllowed, gmsAllowed) + user.gmsAllowed = gmsAllowed + console.log('gmsPublishName:', user.gmsPublishName, gmsPublishName) + user.gmsPublishName = gmsPublishName + if (gmsLocation) { + console.log('1. gmsLocation:', user.location, gmsLocation) + user.location = Location2Point(gmsLocation) + console.log('2. gmsLocation:', user.location) + } + console.log('gmsPublishLocation:', user.gmsPublishLocation, gmsPublishLocation) + user.gmsPublishLocation = gmsPublishLocation + console.log('vor commit user:', user) + } catch (err) { + console.log('error:', err) + } const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') try { await queryRunner.manager.save(user).catch((error) => { + console.log('Error on saving user:', error) throw new LogError('Error saving user', error) }) await queryRunner.commitTransaction() - logger.debug('writing User data successful...') + logger.debug('writing User data successful...', user) } catch (e) { await queryRunner.rollbackTransaction() throw new LogError('Error on writing updated user data', e) diff --git a/backend/src/graphql/validator/Location.ts b/backend/src/graphql/validator/Location.ts new file mode 100644 index 000000000..1333a626e --- /dev/null +++ b/backend/src/graphql/validator/Location.ts @@ -0,0 +1,31 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator' + +import { Location } from '@model/Location' + +import { Location2Point } from '@/graphql/resolver/util/Location2Point' + +export function isValidLocation(validationOptions?: ValidationOptions) { + // eslint-disable-next-line @typescript-eslint/ban-types + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isValidLocation', + target: object.constructor, + propertyName, + options: validationOptions, + validator: { + validate(value: Location) { + // console.log('isValidLocation:', value, value.getPoint()) + if (!value || Location2Point(value).type === 'Point') { + console.log('isValidLocation: true') + return true + } + console.log('isValidLocation: false') + return false + }, + defaultMessage(args: ValidationArguments) { + return `${propertyName} must be a valid Location, ${args.property}` + }, + }, + }) + } +}