diff --git a/backend/.env.dist b/backend/.env.dist index ffcc0882a..a3cf28bd4 100644 --- a/backend/.env.dist +++ b/backend/.env.dist @@ -68,10 +68,11 @@ FEDERATION_XCOM_SENDCOINS_ENABLED=false # GMS # GMS_ACTIVE=true # Coordinates of Illuminz test instance -#GMS_URL=http://54.176.169.179:3071 -GMS_URL=http://localhost:4044/ +#GMS_API_URL=http://54.176.169.179:3071 +GMS_API_URL=http://localhost:4044/ +GMS_DASHBOARD_URL=http://localhost:8080/ # HUMHUB HUMHUB_ACTIVE=false #HUMHUB_API_URL=https://community.gradido.net/ -#HUMHUB_JWT_KEY= \ No newline at end of file +#HUMHUB_JWT_KEY= diff --git a/backend/.env.template b/backend/.env.template index bb68d85f6..71fbcbf31 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -66,9 +66,13 @@ FEDERATION_XCOM_SENDCOINS_ENABLED=$FEDERATION_XCOM_SENDCOINS_ENABLED # GMS GMS_ACTIVE=$GMS_ACTIVE -GMS_URL=$GMS_URL +GMS_API_URL=$GMS_API_URL +GMS_DASHBOARD_URL=$GMS_DASHBOARD_URL +GMS_WEBHOOK_SECRET=$GMS_WEBHOOK_SECRET +GMS_CREATE_USER_THROW_ERRORS=$GMS_CREATE_USER_THROW_ERRORS # HUMHUB HUMHUB_ACTIVE=$HUMHUB_ACTIVE HUMHUB_API_URL=$HUMHUB_API_URL -HUMHUB_JWT_KEY=$HUMHUB_JWT_KEY \ No newline at end of file +HUMHUB_JWT_KEY=$HUMHUB_JWT_KEY + diff --git a/backend/src/apis/gms/GmsClient.ts b/backend/src/apis/gms/GmsClient.ts index 32a3802ff..a59f7f6b5 100644 --- a/backend/src/apis/gms/GmsClient.ts +++ b/backend/src/apis/gms/GmsClient.ts @@ -7,12 +7,13 @@ import axios from 'axios' import { CONFIG } from '@/config' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' +import { ensureUrlEndsWithSlash } from '@/util/utilities' import { GmsUser } from './model/GmsUser' /* export async function communityList(): Promise { - const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_URL) const service = 'community/list?page=1&perPage=20' const config = { headers: { @@ -44,7 +45,7 @@ export async function communityList(): Promise { - const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_URL) const service = 'community-user/list?page=1&perPage=20' const config = { headers: { @@ -80,7 +81,7 @@ export async function userList(): Promise { } export async function userByUuid(uuid: string): Promise { - const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_URL) const service = 'community-user/list?page=1&perPage=20' const config = { headers: { @@ -118,7 +119,7 @@ export async function userByUuid(uuid: string): Promise { if (CONFIG.GMS_ACTIVE) { - const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_API_URL) const service = 'community-user' const config = { headers: { @@ -152,7 +153,7 @@ export async function createGmsUser(apiKey: string, user: GmsUser): Promise { if (CONFIG.GMS_ACTIVE) { - const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_API_URL) const service = 'community-user' const config = { headers: { @@ -189,7 +190,7 @@ export async function verifyAuthToken( communityUuid: string, token: string, ): Promise { - const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_API_URL) const service = 'verify-auth-token?token='.concat(token).concat('&uuid=').concat(communityUuid) const config = { headers: { diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index a73a3be70..82308a52b 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -12,14 +12,14 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0083-join_community_federated_communities', + DB_VERSION: '0084-introduce_humhub_registration', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v22.2024-03-14', + EXPECTED: 'v23.2024-04-04', CURRENT: '', }, } @@ -145,7 +145,8 @@ 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/', + GMS_API_URL: process.env.GMS_API_URL ?? 'http://localhost:4044/', + GMS_DASHBOARD_URL: process.env.GMS_DASHBOARD_URL ?? 'http://localhost:8080/', // used as secret postfix attached at the gms community-auth-url endpoint ('/hook/gms/' + 'secret') GMS_WEBHOOK_SECRET: process.env.GMS_WEBHOOK_SECRET ?? 'secret', } diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 8da8306fd..56899d4b0 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -6,6 +6,7 @@ import { CONFIG } from '@/config' // eslint-disable-next-line camelcase import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient' import { backendLogger as logger } from '@/server/logger' +import { ensureUrlEndsWithSlash } from '@/util/utilities' import { OpenConnectionArgs } from './client/1_0/model/OpenConnectionArgs' import { AuthenticationClientFactory } from './client/AuthenticationClientFactory' @@ -39,9 +40,7 @@ export async function startCommunityAuthentication( const args = new OpenConnectionArgs() args.publicKey = homeCom.publicKey.toString('hex') // TODO encrypt url with foreignCom.publicKey and sign it with homeCom.privateKey - args.url = homeFedCom.endPoint.endsWith('/') - ? homeFedCom.endPoint - : homeFedCom.endPoint + '/' + homeFedCom.apiVersion + args.url = ensureUrlEndsWithSlash(homeFedCom.endPoint).concat(homeFedCom.apiVersion) logger.debug( 'Authentication: before client.openConnection() args:', homeCom.publicKey.toString('hex'), diff --git a/backend/src/federation/client/1_0/AuthenticationClient.ts b/backend/src/federation/client/1_0/AuthenticationClient.ts index f73393255..c1d921823 100644 --- a/backend/src/federation/client/1_0/AuthenticationClient.ts +++ b/backend/src/federation/client/1_0/AuthenticationClient.ts @@ -2,6 +2,7 @@ import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCom import { GraphQLClient } from 'graphql-request' import { backendLogger as logger } from '@/server/logger' +import { ensureUrlEndsWithSlash } from '@/util/utilities' import { OpenConnectionArgs } from './model/OpenConnectionArgs' import { openConnection } from './query/openConnection' @@ -13,9 +14,7 @@ export class AuthenticationClient { constructor(dbCom: DbFederatedCommunity) { this.dbCom = dbCom - this.endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${ - dbCom.apiVersion - }/` + this.endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion).concat('/') this.client = new GraphQLClient(this.endpoint, { method: 'POST', jsonSerializer: { diff --git a/backend/src/federation/client/1_0/FederationClient.ts b/backend/src/federation/client/1_0/FederationClient.ts index b9939a12c..0c2b4101b 100644 --- a/backend/src/federation/client/1_0/FederationClient.ts +++ b/backend/src/federation/client/1_0/FederationClient.ts @@ -4,6 +4,7 @@ import { GraphQLClient } from 'graphql-request' import { getPublicCommunityInfo } from '@/federation/client/1_0/query/getPublicCommunityInfo' import { getPublicKey } from '@/federation/client/1_0/query/getPublicKey' import { backendLogger as logger } from '@/server/logger' +import { ensureUrlEndsWithSlash } from '@/util/utilities' import { PublicCommunityInfoLoggingView } from './logging/PublicCommunityInfoLogging.view' import { GetPublicKeyResult } from './model/GetPublicKeyResult' @@ -16,9 +17,7 @@ export class FederationClient { constructor(dbCom: DbFederatedCommunity) { this.dbCom = dbCom - this.endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${ - dbCom.apiVersion - }/` + this.endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion).concat('/') this.client = new GraphQLClient(this.endpoint, { method: 'GET', jsonSerializer: { diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index bcf303584..2c3fcce4c 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -3,6 +3,7 @@ import { GraphQLClient } from 'graphql-request' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' +import { ensureUrlEndsWithSlash } from '@/util/utilities' import { SendCoinsArgsLoggingView } from './logging/SendCoinsArgsLogging.view' import { SendCoinsResultLoggingView } from './logging/SendCoinsResultLogging.view' @@ -20,9 +21,7 @@ export class SendCoinsClient { constructor(dbCom: DbFederatedCommunity) { this.dbCom = dbCom - this.endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${ - dbCom.apiVersion - }/` + this.endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion).concat('/') this.client = new GraphQLClient(this.endpoint, { method: 'POST', jsonSerializer: { diff --git a/backend/src/federation/client/FederationClientFactory.ts b/backend/src/federation/client/FederationClientFactory.ts index fe2ff0dbd..6010fa5eb 100644 --- a/backend/src/federation/client/FederationClientFactory.ts +++ b/backend/src/federation/client/FederationClientFactory.ts @@ -5,6 +5,7 @@ import { FederationClient as V1_0_FederationClient } from '@/federation/client/1 // eslint-disable-next-line camelcase import { FederationClient as V1_1_FederationClient } from '@/federation/client/1_1/FederationClient' import { ApiVersionType } from '@/federation/enum/apiVersionType' +import { ensureUrlEndsWithSlash } from '@/util/utilities' // eslint-disable-next-line camelcase type FederationClient = V1_0_FederationClient | V1_1_FederationClient @@ -47,10 +48,7 @@ export class FederationClientFactory { const instance = FederationClientFactory.instanceArray.find( (instance) => instance.id === dbCom.id, ) - // TODO: found a way to prevent double code with FederationClient::constructor - const endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${ - dbCom.apiVersion - }/` + const endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion) // check if endpoint is still the same and not changed meanwhile if (instance && instance.client.getEndpoint() === endpoint) { return instance.client diff --git a/backend/src/graphql/arg/UpdateUserInfosArgs.ts b/backend/src/graphql/arg/UpdateUserInfosArgs.ts index a6c80cddd..d3e3dc744 100644 --- a/backend/src/graphql/arg/UpdateUserInfosArgs.ts +++ b/backend/src/graphql/arg/UpdateUserInfosArgs.ts @@ -46,6 +46,10 @@ export class UpdateUserInfosArgs { @IsBoolean() hideAmountGDT?: boolean + @Field({ nullable: true }) + @IsBoolean() + humhubAllowed?: boolean + @Field({ nullable: true }) @IsBoolean() gmsAllowed?: boolean @@ -54,6 +58,10 @@ export class UpdateUserInfosArgs { @IsEnum(GmsPublishNameType) gmsPublishName?: GmsPublishNameType | null + @Field(() => GmsPublishNameType, { nullable: true }) + @IsEnum(GmsPublishNameType) + humhubPublishName?: GmsPublishNameType | null + @Field(() => Location, { nullable: true }) @isValidLocation() gmsLocation?: Location | null diff --git a/backend/src/graphql/model/FederatedCommunity.ts b/backend/src/graphql/model/FederatedCommunity.ts index fb30b0292..01a3996ce 100644 --- a/backend/src/graphql/model/FederatedCommunity.ts +++ b/backend/src/graphql/model/FederatedCommunity.ts @@ -1,6 +1,8 @@ import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { ObjectType, Field, Int } from 'type-graphql' +import { ensureUrlEndsWithSlash } from '@/util/utilities' + @ObjectType() export class FederatedCommunity { constructor(dbCom: DbFederatedCommunity) { @@ -8,7 +10,7 @@ export class FederatedCommunity { this.foreign = dbCom.foreign this.publicKey = dbCom.publicKey.toString('hex') this.apiVersion = dbCom.apiVersion - this.endPoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' + this.endPoint = ensureUrlEndsWithSlash(dbCom.endPoint) this.lastAnnouncedAt = dbCom.lastAnnouncedAt this.verifiedAt = dbCom.verifiedAt this.lastErrorAt = dbCom.lastErrorAt diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 166367fd1..a6a5ad199 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -32,8 +32,10 @@ export class User { this.hasElopage = null this.hideAmountGDD = user.hideAmountGDD this.hideAmountGDT = user.hideAmountGDT + this.humhubAllowed = user.humhubAllowed this.gmsAllowed = user.gmsAllowed this.gmsPublishName = user.gmsPublishName + this.humhubPublishName = user.humhubPublishName this.gmsPublishLocation = user.gmsPublishLocation } } @@ -80,12 +82,18 @@ export class User { @Field(() => Boolean) hideAmountGDT: boolean + @Field(() => Boolean) + humhubAllowed: boolean + @Field(() => Boolean) gmsAllowed: boolean @Field(() => GmsPublishNameType, { nullable: true }) gmsPublishName: GmsPublishNameType | null + @Field(() => GmsPublishNameType, { nullable: true }) + humhubPublishName: GmsPublishNameType | null + @Field(() => GmsPublishLocationType, { nullable: true }) gmsPublishLocation: GmsPublishLocationType | null diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index e7c873fc4..4fdf387b7 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -183,7 +183,9 @@ describe('UserResolver', () => { communityUuid: homeCom.communityUuid, foreign: false, gmsAllowed: true, + humhubAllowed: false, gmsPublishName: 0, + humhubPublishName: 0, gmsPublishLocation: 2, location: null, gmsRegistered: false, diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 7c11776df..436c0aa83 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -394,8 +394,8 @@ export class UserResolver { logger.addContext('user', 'unknown') logger.info(`forgotPassword(${email})...`) email = email.trim().toLowerCase() - const user = await findUserByEmail(email).catch(() => { - logger.warn(`fail on find UserContact per ${email}`) + const user = await findUserByEmail(email).catch((error) => { + logger.warn(`fail on find UserContact per ${email} because: ${error}`) }) if (!user || user.deletedAt) { @@ -559,8 +559,10 @@ export class UserResolver { passwordNew, hideAmountGDD, hideAmountGDT, + humhubAllowed, gmsAllowed, gmsPublishName, + humhubPublishName, gmsLocation, gmsPublishLocation, } = updateUserInfosArgs @@ -617,12 +619,18 @@ export class UserResolver { if (hideAmountGDT !== undefined) { user.hideAmountGDT = hideAmountGDT } + if (humhubAllowed !== undefined) { + user.humhubAllowed = humhubAllowed + } if (gmsAllowed !== undefined) { user.gmsAllowed = gmsAllowed } if (gmsPublishName !== null && gmsPublishName !== undefined) { user.gmsPublishName = gmsPublishName } + if (humhubPublishName !== null && humhubPublishName !== undefined) { + user.humhubPublishName = humhubPublishName + } if (gmsLocation) { user.location = Location2Point(gmsLocation) } diff --git a/backend/src/graphql/resolver/util/authenticateGmsUserPlayground.ts b/backend/src/graphql/resolver/util/authenticateGmsUserPlayground.ts index cad98c683..ef3c199c9 100644 --- a/backend/src/graphql/resolver/util/authenticateGmsUserPlayground.ts +++ b/backend/src/graphql/resolver/util/authenticateGmsUserPlayground.ts @@ -4,13 +4,16 @@ import { verifyAuthToken } from '@/apis/gms/GmsClient' import { CONFIG } from '@/config' import { GmsUserAuthenticationResult } from '@/graphql/model/GmsUserAuthenticationResult' import { backendLogger as logger } from '@/server/logger' +import { ensureUrlEndsWithSlash } from '@/util/utilities' export async function authenticateGmsUserPlayground( token: string, dbUser: DbUser, ): Promise { const result = new GmsUserAuthenticationResult() - result.url = CONFIG.GMS_URL.concat('/playground') + const dashboardUrl = ensureUrlEndsWithSlash(CONFIG.GMS_DASHBOARD_URL) + + result.url = dashboardUrl.concat('playground') result.token = await verifyAuthToken(dbUser.communityUuid, token) logger.info('GmsUserAuthenticationResult:', result) return result diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 732c585d0..45a142ce8 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -52,7 +52,9 @@ const communityDbUser: dbUser = { communityUuid: '55555555-4444-4333-2222-11111111', community: null, gmsPublishName: 0, + humhubPublishName: 0, gmsAllowed: false, + humhubAllowed: false, location: null, gmsPublishLocation: 2, gmsRegistered: false, diff --git a/backend/src/util/utilities.ts b/backend/src/util/utilities.ts index c3895cb9e..bc2c2198a 100644 --- a/backend/src/util/utilities.ts +++ b/backend/src/util/utilities.ts @@ -29,3 +29,7 @@ export function resetInterface>(obj: T): T { } return obj } + +export const ensureUrlEndsWithSlash = (url: string): string => { + return url.endsWith('/') ? url : url.concat('/') +} diff --git a/database/entity/0084-introduce_humhub_registration/User.ts b/database/entity/0084-introduce_humhub_registration/User.ts new file mode 100644 index 000000000..a375f6748 --- /dev/null +++ b/database/entity/0084-introduce_humhub_registration/User.ts @@ -0,0 +1,176 @@ +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + DeleteDateColumn, + OneToMany, + JoinColumn, + OneToOne, + Geometry, + ManyToOne, +} from 'typeorm' +import { Contribution } from '../Contribution' +import { ContributionMessage } from '../ContributionMessage' +import { UserContact } from '../UserContact' +import { UserRole } from '../UserRole' +import { GeometryTransformer } from '../../src/typeorm/GeometryTransformer' +import { Community } from '../Community' + +@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) +export class User extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ type: 'bool', default: false }) + foreign: boolean + + @Column({ + name: 'gradido_id', + length: 36, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + gradidoID: string + + @Column({ + name: 'community_uuid', + type: 'char', + length: 36, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + communityUuid: string + + @ManyToOne(() => Community, (community) => community.users) + @JoinColumn({ name: 'community_uuid', referencedColumnName: 'communityUuid' }) + community: Community | null + + @Column({ + name: 'alias', + length: 20, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + alias: string + + @OneToOne(() => UserContact, (emailContact: UserContact) => emailContact.user) + @JoinColumn({ name: 'email_id' }) + emailContact: UserContact + + @Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null }) + emailId: number | null + + @Column({ + name: 'first_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + firstName: string + + @Column({ + name: 'last_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + lastName: string + + @Column({ name: 'gms_publish_name', type: 'int', unsigned: true, nullable: false, default: 0 }) + gmsPublishName: number + + @Column({ name: 'humhub_publish_name', type: 'int', unsigned: true, nullable: false, default: 0 }) + humhubPublishName: number + + @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false }) + createdAt: Date + + @DeleteDateColumn({ name: 'deleted_at', nullable: true }) + deletedAt: Date | null + + @Column({ type: 'bigint', default: 0, unsigned: true }) + password: BigInt + + @Column({ + name: 'password_encryption_type', + type: 'int', + unsigned: true, + nullable: false, + default: 0, + }) + passwordEncryptionType: number + + @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false }) + language: string + + @Column({ type: 'bool', default: false }) + hideAmountGDD: boolean + + @Column({ type: 'bool', default: false }) + hideAmountGDT: boolean + + @OneToMany(() => UserRole, (userRole) => userRole.user) + @JoinColumn({ name: 'user_id' }) + userRoles: UserRole[] + + @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) + referrerId?: number | null + + @Column({ + name: 'contribution_link_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + contributionLinkId?: number | null + + @Column({ name: 'publisher_id', default: 0 }) + publisherId: number + + @Column({ name: 'gms_allowed', type: 'bool', default: true }) + gmsAllowed: boolean + + @Column({ + name: 'location', + type: 'geometry', + default: null, + nullable: true, + transformer: GeometryTransformer, + }) + location: Geometry | null + + @Column({ + name: 'gms_publish_location', + type: 'int', + unsigned: true, + nullable: false, + default: 2, + }) + gmsPublishLocation: number + + @Column({ name: 'gms_registered', type: 'bool', default: false }) + gmsRegistered: boolean + + @Column({ name: 'gms_registered_at', type: 'datetime', default: null, nullable: true }) + gmsRegisteredAt: Date | null + + @Column({ name: 'humhub_allowed', type: 'bool', default: false }) + humhubAllowed: boolean + + @OneToMany(() => Contribution, (contribution) => contribution.user) + @JoinColumn({ name: 'user_id' }) + contributions?: Contribution[] + + @OneToMany(() => ContributionMessage, (message) => message.user) + @JoinColumn({ name: 'user_id' }) + messages?: ContributionMessage[] + + @OneToMany(() => UserContact, (userContact: UserContact) => userContact.user) + @JoinColumn({ name: 'user_id' }) + userContacts?: UserContact[] +} diff --git a/database/entity/User.ts b/database/entity/User.ts index e3f15113d..993d983ef 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0082-introduce_gms_registration/User' +export { User } from './0084-introduce_humhub_registration/User' diff --git a/database/migrations/0084-introduce_humhub_registration.ts b/database/migrations/0084-introduce_humhub_registration.ts new file mode 100644 index 000000000..858283602 --- /dev/null +++ b/database/migrations/0084-introduce_humhub_registration.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `humhub_allowed` tinyint(1) NOT NULL DEFAULT 0 AFTER `gms_registered_at`;', + ) + await queryFn( + 'ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `humhub_publish_name` int unsigned NOT NULL DEFAULT 0 AFTER `gms_publish_name`;', + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `users` DROP COLUMN IF EXISTS `humhub_allowed`;') + await queryFn('ALTER TABLE `users` DROP COLUMN IF EXISTS `humhub_publish_name`;') +} diff --git a/deployment/bare_metal/.env.dist b/deployment/bare_metal/.env.dist index 68280c096..341bae554 100644 --- a/deployment/bare_metal/.env.dist +++ b/deployment/bare_metal/.env.dist @@ -122,11 +122,13 @@ WEBHOOK_ELOPAGE_SECRET=secret # GMS GMS_ACTIVE=false # Coordinates of Illuminz test instance -#GMS_URL=http://54.176.169.179:3071 -GMS_URL=http://localhost:4044/ +#GMS_API_URL=http://54.176.169.179:3071 +GMS_API_URL=http://localhost:4044/ +GMS_DASHBOARD_URL=http://localhost:8080/ +GMS_WEBHOOK_SECRET=secret +GMS_CREATE_USER_THROW_ERRORS=false # HUMHUB HUMHUB_ACTIVE=false #HUMHUB_API_URL=https://community.gradido.net #HUMHUB_JWT_KEY= - diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 4e66aa5f9..f557eee83 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv' dotenv.config() const constants = { - DB_VERSION: '0083-join_community_federated_communities', + DB_VERSION: '0084-introduce_humhub_registration', LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index f8992a360..26b727841 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0083-join_community_federated_communities', + DB_VERSION: '0084-introduce_humhub_registration', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/frontend/.env.dist b/frontend/.env.dist index f680d9a50..f11a70873 100644 --- a/frontend/.env.dist +++ b/frontend/.env.dist @@ -23,3 +23,4 @@ META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Econo META_AUTHOR="Bernd Hückstädt - Gradido-Akademie" GMS_ACTIVE=false +HUMHUB_ACTIVE=false diff --git a/frontend/.env.template b/frontend/.env.template index c121545da..40d0e7ee1 100644 --- a/frontend/.env.template +++ b/frontend/.env.template @@ -25,4 +25,5 @@ META_KEYWORDS_DE=$META_KEYWORDS_DE META_KEYWORDS_EN=$META_KEYWORDS_EN META_AUTHOR=$META_AUTHOR -GMS_ACTIVE=$GMS_ACTIVE \ No newline at end of file +GMS_ACTIVE=$GMS_ACTIVE +HUMHUB_ACTIVE=$HUMHUB_ACTIVE \ No newline at end of file diff --git a/frontend/public/img/loupe.png b/frontend/public/img/loupe.png new file mode 100644 index 000000000..ab289e4ad Binary files /dev/null and b/frontend/public/img/loupe.png differ diff --git a/frontend/src/components/Menu/Sidebar.spec.js b/frontend/src/components/Menu/Sidebar.spec.js index 23c855557..77a525f3b 100644 --- a/frontend/src/components/Menu/Sidebar.spec.js +++ b/frontend/src/components/Menu/Sidebar.spec.js @@ -34,7 +34,7 @@ describe('Sidebar', () => { describe('the genaral section', () => { it('has six nav-items', () => { - expect(wrapper.findAll('ul').at(0).findAll('.nav-item')).toHaveLength(6) + expect(wrapper.findAll('ul').at(0).findAll('.nav-item')).toHaveLength(7) }) it('has nav-item "navigation.overview" in navbar', () => { @@ -60,6 +60,10 @@ describe('Sidebar', () => { it('has nav-item "navigation.info" in navbar', () => { expect(wrapper.findAll('.nav-item').at(5).text()).toContain('navigation.info') }) + + it('has nav-item "usersearch" in navbar', () => { + expect(wrapper.findAll('.nav-item').at(6).text()).toContain('navigation.usersearch') + }) }) describe('the specific section', () => { diff --git a/frontend/src/components/Menu/Sidebar.vue b/frontend/src/components/Menu/Sidebar.vue index 1b18acc17..227275786 100644 --- a/frontend/src/components/Menu/Sidebar.vue +++ b/frontend/src/components/Menu/Sidebar.vue @@ -28,10 +28,14 @@ {{ $t('GDT') }} - + {{ $t('navigation.info') }} + + + {{ $t('navigation.usersearch') }} +
diff --git a/frontend/src/components/UserSettings/UserGMSSwitch.vue b/frontend/src/components/UserSettings/UserGMSSwitch.vue deleted file mode 100644 index 355beeff2..000000000 --- a/frontend/src/components/UserSettings/UserGMSSwitch.vue +++ /dev/null @@ -1,45 +0,0 @@ - - diff --git a/frontend/src/components/UserSettings/UserGMSNamingFormat.spec.js b/frontend/src/components/UserSettings/UserNamingFormat.spec.js similarity index 79% rename from frontend/src/components/UserSettings/UserGMSNamingFormat.spec.js rename to frontend/src/components/UserSettings/UserNamingFormat.spec.js index 3dbbfdb2c..f07f5e3f2 100644 --- a/frontend/src/components/UserSettings/UserGMSNamingFormat.spec.js +++ b/frontend/src/components/UserSettings/UserNamingFormat.spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils' -import UserGMSNamingFormat from './UserGMSNamingFormat.vue' +import UserNamingFormat from './UserNamingFormat.vue' import { toastErrorSpy } from '@test/testSetup' const mockAPIcall = jest.fn() @@ -8,10 +8,10 @@ const storeCommitMock = jest.fn() const localVue = global.localVue -describe('UserGMSNamingFormat', () => { +describe('UserNamingFormat', () => { let wrapper beforeEach(() => { - wrapper = mount(UserGMSNamingFormat, { + wrapper = mount(UserNamingFormat, { mocks: { $t: (key) => key, // Mocking the translation function $store: { @@ -27,6 +27,9 @@ describe('UserGMSNamingFormat', () => { localVue, propsData: { selectedOption: 'GMS_PUBLISH_NAME_ALIAS_OR_INITALS', + initialValue: 'GMS_PUBLISH_NAME_ALIAS_OR_INITALS', + attrName: 'gmsPublishName', + successMessage: 'success message', }, }) }) @@ -53,16 +56,16 @@ describe('UserGMSNamingFormat', () => { 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']) + expect(wrapper.emitted().valueChanged).toBeTruthy() + expect(wrapper.emitted().valueChanged.length).toBe(1) + expect(wrapper.emitted().valueChanged[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() + expect(wrapper.emitted().valueChanged).toBeFalsy() }) describe('update with error', () => { diff --git a/frontend/src/components/UserSettings/UserGMSNamingFormat.vue b/frontend/src/components/UserSettings/UserNamingFormat.vue similarity index 77% rename from frontend/src/components/UserSettings/UserGMSNamingFormat.vue rename to frontend/src/components/UserSettings/UserNamingFormat.vue index 29b4cd384..510541219 100644 --- a/frontend/src/components/UserSettings/UserGMSNamingFormat.vue +++ b/frontend/src/components/UserSettings/UserNamingFormat.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/pages/UserSearch.spec.js b/frontend/src/pages/UserSearch.spec.js new file mode 100644 index 000000000..7a991ac0f --- /dev/null +++ b/frontend/src/pages/UserSearch.spec.js @@ -0,0 +1,73 @@ +import { mount } from '@vue/test-utils' +import UserSearch from './UserSearch' +import { toastErrorSpy } from '../../test/testSetup' +import { authenticateGmsUserSearch } from '@/graphql/queries' + +const localVue = global.localVue + +window.scrollTo = jest.fn() + +const apolloQueryMock = jest + .fn() + .mockResolvedValueOnce({ + data: { + authenticateGmsUserSearch: { + gmsUri: 'http://localhost:8080/playground?not initialized', + }, + }, + }) + .mockResolvedValue('default') + +describe('UserSearch', () => { + let wrapper + + const mocks = { + $t: jest.fn((t) => t), + $n: jest.fn(), + $i18n: { + locale: 'en', + }, + $apollo: { + query: apolloQueryMock, + }, + } + + const Wrapper = () => { + return mount(UserSearch, { + localVue, + mocks, + }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the usersearch page', () => { + expect(wrapper.find('div.usersearch').exists()).toBeTruthy() + }) + + it('calls authenticateGmsUserSearch', () => { + expect(apolloQueryMock).toBeCalledWith( + expect.objectContaining({ + query: authenticateGmsUserSearch, + }), + ) + }) + + describe('error apolloQueryMock', () => { + beforeEach(async () => { + jest.clearAllMocks() + apolloQueryMock.mockRejectedValue({ + message: 'uups', + }) + wrapper = Wrapper() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('authenticateGmsUserSearch failed!') + }) + }) + }) +}) diff --git a/frontend/src/pages/UserSearch.vue b/frontend/src/pages/UserSearch.vue new file mode 100644 index 000000000..070a96d7b --- /dev/null +++ b/frontend/src/pages/UserSearch.vue @@ -0,0 +1,56 @@ + + diff --git a/frontend/src/routes/router.test.js b/frontend/src/routes/router.test.js index 6d7e7b2a0..af6b1c431 100644 --- a/frontend/src/routes/router.test.js +++ b/frontend/src/routes/router.test.js @@ -49,8 +49,8 @@ describe('router', () => { expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' }) }) - it('has 19 routes defined', () => { - expect(routes).toHaveLength(19) + it('has 20 routes defined', () => { + expect(routes).toHaveLength(20) }) describe('overview', () => { @@ -142,6 +142,17 @@ describe('router', () => { }) }) + describe('usersearch', () => { + it('requires authorization', () => { + expect(routes.find((r) => r.path === '/usersearch').meta.requiresAuth).toBeTruthy() + }) + + it('loads the "UserSearch" page', async () => { + const component = await routes.find((r) => r.path === '/usersearch').component() + expect(component.default.name).toBe('UserSearch') + }) + }) + describe('gdt', () => { it('requires authorization', () => { expect(routes.find((r) => r.path === '/gdt').meta.requiresAuth).toBeTruthy() diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index 869151618..d8f85b858 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -80,6 +80,14 @@ const routes = [ pageTitle: 'information', }, }, + { + path: '/usersearch', + component: () => import('@/pages/UserSearch'), + meta: { + requiresAuth: true, + pageTitle: 'usersearch', + }, + }, // { // path: '/storys', // component: () => import('@/pages/TopStorys'), diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index e9eab2dd7..e574aa008 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -38,9 +38,15 @@ export const mutations = { gmsAllowed: (state, gmsAllowed) => { state.gmsAllowed = gmsAllowed }, + humhubAllowed: (state, humhubAllowed) => { + state.humhubAllowed = humhubAllowed + }, gmsPublishName: (state, gmsPublishName) => { state.gmsPublishName = gmsPublishName }, + humhubPublishName: (state, humhubPublishName) => { + state.humhubPublishName = humhubPublishName + }, gmsPublishLocation: (state, gmsPublishLocation) => { state.gmsPublishLocation = gmsPublishLocation }, @@ -81,7 +87,9 @@ export const actions = { commit('lastName', data.lastName) commit('newsletterState', data.klickTipp.newsletterState) commit('gmsAllowed', data.gmsAllowed) + commit('humhubAllowed', data.humhubAllowed) commit('gmsPublishName', data.gmsPublishName) + commit('humhubPublishName', data.humhubPublishName) commit('gmsPublishLocation', data.gmsPublishLocation) commit('hasElopage', data.hasElopage) commit('publisherId', data.publisherId) @@ -98,7 +106,9 @@ export const actions = { commit('lastName', '') commit('newsletterState', null) commit('gmsAllowed', null) + commit('humhubAllowed', null) commit('gmsPublishName', null) + commit('humhubPublishName', null) commit('gmsPublishLocation', null) commit('hasElopage', false) commit('publisherId', null) @@ -133,7 +143,9 @@ try { roles: [], newsletterState: null, gmsAllowed: null, + humhubAllowed: null, gmsPublishName: null, + humhubPublishName: null, gmsPublishLocation: null, hasElopage: false, publisherId: null, diff --git a/frontend/src/store/store.test.js b/frontend/src/store/store.test.js index 75ab308fd..6d06a3724 100644 --- a/frontend/src/store/store.test.js +++ b/frontend/src/store/store.test.js @@ -29,7 +29,9 @@ const { username, newsletterState, gmsAllowed, + humhubAllowed, gmsPublishName, + humhubPublishName, gmsPublishLocation, publisherId, roles, @@ -133,6 +135,14 @@ describe('Vuex store', () => { }) }) + describe('humhubAllowed', () => { + it('sets the state of humhubAllowed', () => { + const state = { humhubAllowed: null } + humhubAllowed(state, true) + expect(state.humhubAllowed).toEqual(true) + }) + }) + describe('gmsPublishName', () => { it('sets gmsPublishName', () => { const state = { gmsPublishName: null } @@ -141,6 +151,14 @@ describe('Vuex store', () => { }) }) + describe('humhubPublishName', () => { + it('sets humhubPublishName', () => { + const state = { humhubPublishName: null } + humhubPublishName(state, 'GMS_PUBLISH_NAME_INITIALS') + expect(state.humhubPublishName).toEqual('GMS_PUBLISH_NAME_INITIALS') + }) + }) + describe('gmsPublishLocation', () => { it('sets gmsPublishLocation', () => { const state = { gmsPublishLocation: null } @@ -218,7 +236,9 @@ describe('Vuex store', () => { newsletterState: true, }, gmsAllowed: true, + humhubAllowed: false, gmsPublishName: 'GMS_PUBLISH_NAME_FULL', + humhubPublishName: 'GMS_PUBLISH_NAME_FULL', gmsPublishLocation: 'GMS_LOCATION_TYPE_EXACT', hasElopage: false, publisherId: 1234, @@ -227,9 +247,9 @@ describe('Vuex store', () => { hideAmountGDT: true, } - it('calls fifteen commits', () => { + it('calls seventeen commits', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenCalledTimes(15) + expect(commit).toHaveBeenCalledTimes(17) }) it('commits gradidoID', () => { @@ -267,39 +287,49 @@ describe('Vuex store', () => { expect(commit).toHaveBeenNthCalledWith(7, 'gmsAllowed', true) }) + it('commits humhubAllowed', () => { + login({ commit, state }, commitedData) + expect(commit).toHaveBeenNthCalledWith(8, 'humhubAllowed', false) + }) + it('commits gmsPublishName', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(8, 'gmsPublishName', 'GMS_PUBLISH_NAME_FULL') + expect(commit).toHaveBeenNthCalledWith(9, 'gmsPublishName', 'GMS_PUBLISH_NAME_FULL') + }) + + it('commits humhubPublishName', () => { + login({ commit, state }, commitedData) + expect(commit).toHaveBeenNthCalledWith(10, 'humhubPublishName', 'GMS_PUBLISH_NAME_FULL') }) it('commits gmsPublishLocation', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(9, 'gmsPublishLocation', 'GMS_LOCATION_TYPE_EXACT') + expect(commit).toHaveBeenNthCalledWith(11, 'gmsPublishLocation', 'GMS_LOCATION_TYPE_EXACT') }) it('commits hasElopage', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(10, 'hasElopage', false) + expect(commit).toHaveBeenNthCalledWith(12, 'hasElopage', false) }) it('commits publisherId', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(11, 'publisherId', 1234) + expect(commit).toHaveBeenNthCalledWith(13, 'publisherId', 1234) }) it('commits roles', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(12, 'roles', ['admin']) + expect(commit).toHaveBeenNthCalledWith(14, 'roles', ['admin']) }) it('commits hideAmountGDD', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(13, 'hideAmountGDD', false) + expect(commit).toHaveBeenNthCalledWith(15, 'hideAmountGDD', false) }) it('commits hideAmountGDT', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenNthCalledWith(14, 'hideAmountGDT', true) + expect(commit).toHaveBeenNthCalledWith(16, 'hideAmountGDT', true) }) }) @@ -307,9 +337,9 @@ describe('Vuex store', () => { const commit = jest.fn() const state = {} - it('calls seventeen commits', () => { + it('calls nineteen commits', () => { logout({ commit, state }) - expect(commit).toHaveBeenCalledTimes(17) + expect(commit).toHaveBeenCalledTimes(19) }) it('commits token', () => { @@ -347,44 +377,54 @@ describe('Vuex store', () => { expect(commit).toHaveBeenNthCalledWith(7, 'gmsAllowed', null) }) + it('commits humhubAllowed', () => { + logout({ commit, state }) + expect(commit).toHaveBeenNthCalledWith(8, 'humhubAllowed', null) + }) + it('commits gmsPublishName', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(8, 'gmsPublishName', null) + expect(commit).toHaveBeenNthCalledWith(9, 'gmsPublishName', null) + }) + + it('commits humhubPublishName', () => { + logout({ commit, state }) + expect(commit).toHaveBeenNthCalledWith(10, 'humhubPublishName', null) }) it('commits gmsPublishLocation', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(9, 'gmsPublishLocation', null) + expect(commit).toHaveBeenNthCalledWith(11, 'gmsPublishLocation', null) }) it('commits hasElopage', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(10, 'hasElopage', false) + expect(commit).toHaveBeenNthCalledWith(12, 'hasElopage', false) }) it('commits publisherId', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(11, 'publisherId', null) + expect(commit).toHaveBeenNthCalledWith(13, 'publisherId', null) }) it('commits roles', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(12, 'roles', null) + expect(commit).toHaveBeenNthCalledWith(14, 'roles', null) }) it('commits hideAmountGDD', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(13, 'hideAmountGDD', false) + expect(commit).toHaveBeenNthCalledWith(15, 'hideAmountGDD', false) }) it('commits hideAmountGDT', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(14, 'hideAmountGDT', true) + expect(commit).toHaveBeenNthCalledWith(16, 'hideAmountGDT', true) }) it('commits email', () => { logout({ commit, state }) - expect(commit).toHaveBeenNthCalledWith(15, 'email', '') + expect(commit).toHaveBeenNthCalledWith(17, 'email', '') }) // how to get this working?