From 98a4d55516d1bfac2d72f0ba23a7872ba8be9912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Thu, 27 Apr 2023 03:25:13 +0200 Subject: [PATCH 01/49] add graphql-client and -endpoint for community-publicInfo --- .../client/1_0/FederationClientImpl.ts | 83 +++++++++++++++++++ .../client/1_1/FederationClientImpl.ts | 83 +++++++++++++++++++ .../src/federation/client/FederationClient.ts | 13 +++ backend/src/federation/validateCommunities.ts | 55 +++++++++--- .../api/1_0/model/GetPublicInfoResult.ts | 26 ++++++ .../api/1_0/resolver/PublicInfoResolver.ts | 18 ++++ 6 files changed, 267 insertions(+), 11 deletions(-) create mode 100644 backend/src/federation/client/1_0/FederationClientImpl.ts create mode 100644 backend/src/federation/client/1_1/FederationClientImpl.ts create mode 100644 backend/src/federation/client/FederationClient.ts create mode 100644 federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts create mode 100644 federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts diff --git a/backend/src/federation/client/1_0/FederationClientImpl.ts b/backend/src/federation/client/1_0/FederationClientImpl.ts new file mode 100644 index 000000000..deb2c959f --- /dev/null +++ b/backend/src/federation/client/1_0/FederationClientImpl.ts @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' +import { gql } from 'graphql-request' + +import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' +import { LogError } from '@/server/LogError' +import { backendLogger as logger } from '@/server/logger' + +// eslint-disable-next-line import/no-relative-parent-imports +import { FederationClient, PublicInfo } from '../FederationClient' + +export class FederationClientImpl implements FederationClient { + public async requestGetPublicKey(dbCom: DbFederatedCommunity): Promise { + let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' + endpoint = `${endpoint}${dbCom.apiVersion}/` + logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) + + const graphQLClient = GraphQLGetClient.getInstance(endpoint) + logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) + const query = gql` + query { + getPublicKey { + publicKey + } + } + ` + const variables = {} + + try { + const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( + query, + variables, + ) + logger.debug(`Response-Data:`, data, errors, extensions, headers, status) + if (data) { + logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) + logger.info(`requestGetPublicKey processed successfully`) + return data.getPublicKey.publicKey + } + logger.warn(`requestGetPublicKey processed without response data`) + } catch (err) { + throw new LogError(`Request-Error:`, err) + } + } + + public async requestGetPublicInfo(dbCom: DbFederatedCommunity): Promise { + let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' + endpoint = `${endpoint}${dbCom.apiVersion}/` + logger.info(`requestGetPublicInfo with endpoint='${endpoint}'...`) + + const graphQLClient = GraphQLGetClient.getInstance(endpoint) + logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) + const query = gql` + query { + getPublicInfo { + name + description + createdAt + publicKey + } + } + ` + const variables = {} + + try { + const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( + query, + variables, + ) + logger.debug(`Response-Data:`, data, errors, extensions, headers, status) + if (data) { + logger.debug(`Response-PublicInfo:`, data.getPublicInfo.publicInfo) + logger.info(`requestGetPublicInfo processed successfully`) + return data.getPublicInfo.publicInfo + } + logger.warn(`requestGetPublicInfo processed without response data`) + } catch (err) { + throw new LogError(`Request-Error:`, err) + } + } +} diff --git a/backend/src/federation/client/1_1/FederationClientImpl.ts b/backend/src/federation/client/1_1/FederationClientImpl.ts new file mode 100644 index 000000000..472edcdbd --- /dev/null +++ b/backend/src/federation/client/1_1/FederationClientImpl.ts @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' +import { gql } from 'graphql-request' + +import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' +import { LogError } from '@/server/LogError' +import { backendLogger as logger } from '@/server/logger' + +// eslint-disable-next-line import/no-relative-parent-imports +import { FederationClient, PublicInfo } from '../FederationClient' + +export class FederationClientImpl implements FederationClient { + async requestGetPublicKey(dbCom: DbFederatedCommunity): Promise { + let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' + endpoint = `${endpoint}${dbCom.apiVersion}/` + logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) + + const graphQLClient = GraphQLGetClient.getInstance(endpoint) + logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) + const query = gql` + query { + getPublicKey { + publicKey + } + } + ` + const variables = {} + + try { + const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( + query, + variables, + ) + logger.debug(`Response-Data:`, data, errors, extensions, headers, status) + if (data) { + logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) + logger.info(`requestGetPublicKey processed successfully`) + return data.getPublicKey.publicKey + } + logger.warn(`requestGetPublicKey processed without response data`) + } catch (err) { + throw new LogError(`Request-Error:`, err) + } + } + + async requestGetPublicInfo(dbCom: DbFederatedCommunity): Promise { + let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' + endpoint = `${endpoint}${dbCom.apiVersion}/` + logger.info(`requestGetPublicInfo with endpoint='${endpoint}'...`) + + const graphQLClient = GraphQLGetClient.getInstance(endpoint) + logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) + const query = gql` + query { + getPublicInfo { + name + description + createdAt + publicKey + } + } + ` + const variables = {} + + try { + const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( + query, + variables, + ) + logger.debug(`Response-Data:`, data, errors, extensions, headers, status) + if (data) { + logger.debug(`Response-PublicInfo:`, data.getPublicInfo.publicInfo) + logger.info(`requestGetPublicInfo processed successfully`) + return data.getPublicInfo.publicInfo + } + logger.warn(`requestGetPublicInfo processed without response data`) + } catch (err) { + throw new LogError(`Request-Error:`, err) + } + } +} diff --git a/backend/src/federation/client/FederationClient.ts b/backend/src/federation/client/FederationClient.ts new file mode 100644 index 000000000..3ddd80cfb --- /dev/null +++ b/backend/src/federation/client/FederationClient.ts @@ -0,0 +1,13 @@ +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' + +export type PublicInfo = { + name: string + description: string + createdAt: Date + publicKey: string +} + +export interface FederationClient { + requestGetPublicKey(dbCom: DbFederatedCommunity): Promise + requestGetPublicInfo(dbCom: DbFederatedCommunity): Promise +} diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index b38f38ee9..da8484318 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -1,15 +1,17 @@ -/** eslint-disable @typescript-eslint/no-unsafe-call */ /** eslint-disable @typescript-eslint/no-unsafe-assignment */ +/** eslint-disable @typescript-eslint/no-unsafe-call */ import { IsNull } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' // eslint-disable-next-line camelcase -import { requestGetPublicKey as v1_0_requestGetPublicKey } from './client/1_0/FederationClient' +import { FederationClientImpl as V1_0_FederationClientImpl } from './client/1_0/FederationClientImpl' // eslint-disable-next-line camelcase -import { requestGetPublicKey as v1_1_requestGetPublicKey } from './client/1_1/FederationClient' +import { FederationClientImpl as V1_1_FederationClientImpl } from './client/1_1/FederationClientImpl' +import { FederationClient, PublicInfo } from './client/FederationClient' import { ApiVersionType } from './enum/apiVersionType' export function startValidateCommunities(timerInterval: number): void { @@ -41,7 +43,10 @@ export async function validateCommunities(): Promise { `Federation: validate publicKey for dbCom: ${dbCom.id} with apiVersion=${dbCom.apiVersion}`, ) try { - const pubKey = await invokeVersionedRequestGetPublicKey(dbCom) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const pubKey = await getVersionedFederationClient(dbCom.apiVersion).requestGetPublicKey( + dbCom, + ) logger.info( 'Federation: received publicKey from endpoint', pubKey, @@ -51,6 +56,17 @@ export async function validateCommunities(): Promise { logger.info(`Federation: matching publicKey: ${pubKey}`) await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) logger.debug(`Federation: updated dbCom: ${JSON.stringify(dbCom)}`) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const pubInfo = await getVersionedFederationClient(dbCom.apiVersion).requestGetPublicInfo( + dbCom, + ) + logger.debug(`Federation: getPublicInfo pubInfo: ${JSON.stringify(pubInfo)}`) + if (pubInfo) { + logger.info(`Federation: write foreign community...`) + await writeForeignCommunity(dbCom, pubInfo) + logger.info(`Federation: write foreign community... successfully`) + } } else { logger.warn( `Federation: received not matching publicKey -> received: ${ @@ -73,19 +89,36 @@ export async function validateCommunities(): Promise { } } +async function writeForeignCommunity( + dbCom: DbFederatedCommunity, + pubInfo: PublicInfo, +): Promise { + if (dbCom && pubInfo) { + const foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.publicKey = dbCom.publicKey + foreignCom.url = dbCom.endPoint + foreignCom.name = pubInfo.name + foreignCom.description = pubInfo.description + foreignCom.creationDate = pubInfo.createdAt + await DbCommunity.save(foreignCom) + } +} + function isLogError(err: unknown) { return err instanceof LogError } -async function invokeVersionedRequestGetPublicKey( - dbCom: DbFederatedCommunity, -): Promise { - switch (dbCom.apiVersion) { +function getVersionedFederationClient(apiVersion: string): FederationClient { + switch (apiVersion) { case ApiVersionType.V1_0: - return v1_0_requestGetPublicKey(dbCom) + // eslint-disable-next-line camelcase + return new V1_0_FederationClientImpl() case ApiVersionType.V1_1: - return v1_1_requestGetPublicKey(dbCom) + // eslint-disable-next-line camelcase + return new V1_1_FederationClientImpl() default: - return undefined + // eslint-disable-next-line camelcase + return new V1_0_FederationClientImpl() } } diff --git a/federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts b/federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts new file mode 100644 index 000000000..102f446ba --- /dev/null +++ b/federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts @@ -0,0 +1,26 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Community as DbCommunity } from '@entity/Community' +import { Field, ObjectType } from 'type-graphql' + +@ObjectType() +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export class GetPublicInfoResult { + constructor(dbCom: DbCommunity) { + this.publicKey = dbCom.publicKey.toString('hex') + this.name = dbCom.name + this.description = dbCom.description + this.createdAt = dbCom.creationDate + } + + @Field(() => String) + name: string | null + + @Field(() => String) + description: string | null + + @Field(() => Date) + createdAt: Date | null + + @Field(() => String) + publicKey: string +} diff --git a/federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts b/federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts new file mode 100644 index 000000000..ad988670b --- /dev/null +++ b/federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts @@ -0,0 +1,18 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Query, Resolver } from 'type-graphql' +import { federationLogger as logger } from '@/server/logger' +import { Community as DbCommunity } from '@entity/Community' +import { GetPublicInfoResult } from '../model/GetPublicInfoResult' + +@Resolver() +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export class PublicInfoResolver { + @Query(() => GetPublicInfoResult) + async getPublicInfo(): Promise { + logger.debug(`getPublicInfo() via apiVersion=1_0 ...`) + const homeCom = await DbCommunity.findOneOrFail({ foreign: false }) + const result = new GetPublicInfoResult(homeCom) + logger.info(`getPublicInfo()-1_0... return publicInfo=${result}`) + return result + } +} From a824ff0876eac665b5a3f4def51ec0425556e01d Mon Sep 17 00:00:00 2001 From: hrsof Date: Tue, 2 May 2023 15:25:21 +0200 Subject: [PATCH 02/49] rework graphql client and endpoint for community public info --- .../federation/client/1_0/FederationClient.ts | 44 ------------------- .../client/1_0/FederationClientImpl.ts | 14 +++--- .../federation/client/1_1/FederationClient.ts | 44 ------------------- .../client/1_1/FederationClientImpl.ts | 17 ++++--- .../src/federation/client/FederationClient.ts | 6 ++- backend/src/federation/validateCommunities.ts | 10 ++--- ...ult.ts => GetPublicCommunityInfoResult.ts} | 2 +- .../resolver/PublicCommunityInfoResolver.ts | 18 ++++++++ .../api/1_0/resolver/PublicInfoResolver.ts | 18 -------- 9 files changed, 47 insertions(+), 126 deletions(-) delete mode 100644 backend/src/federation/client/1_0/FederationClient.ts delete mode 100644 backend/src/federation/client/1_1/FederationClient.ts rename federation/src/graphql/api/1_0/model/{GetPublicInfoResult.ts => GetPublicCommunityInfoResult.ts} (93%) create mode 100644 federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts delete mode 100644 federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts diff --git a/backend/src/federation/client/1_0/FederationClient.ts b/backend/src/federation/client/1_0/FederationClient.ts deleted file mode 100644 index 743d17348..000000000 --- a/backend/src/federation/client/1_0/FederationClient.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { gql } from 'graphql-request' - -import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' -import { LogError } from '@/server/LogError' -import { backendLogger as logger } from '@/server/logger' - -export async function requestGetPublicKey( - dbCom: DbFederatedCommunity, -): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicKey { - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) - logger.info(`requestGetPublicKey processed successfully`) - return data.getPublicKey.publicKey - } - logger.warn(`requestGetPublicKey processed without response data`) - } catch (err) { - throw new LogError(`Request-Error:`, err) - } -} diff --git a/backend/src/federation/client/1_0/FederationClientImpl.ts b/backend/src/federation/client/1_0/FederationClientImpl.ts index deb2c959f..4bd194594 100644 --- a/backend/src/federation/client/1_0/FederationClientImpl.ts +++ b/backend/src/federation/client/1_0/FederationClientImpl.ts @@ -9,7 +9,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' // eslint-disable-next-line import/no-relative-parent-imports -import { FederationClient, PublicInfo } from '../FederationClient' +import { FederationClient, PublicCommunityInfo } from '../FederationClient' export class FederationClientImpl implements FederationClient { public async requestGetPublicKey(dbCom: DbFederatedCommunity): Promise { @@ -45,16 +45,18 @@ export class FederationClientImpl implements FederationClient { } } - public async requestGetPublicInfo(dbCom: DbFederatedCommunity): Promise { + public async requestGetPublicCommunityInfo( + dbCom: DbFederatedCommunity, + ): Promise { let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicInfo with endpoint='${endpoint}'...`) + logger.info(`requestGetPublicCommunityInfo with endpoint='${endpoint}'...`) const graphQLClient = GraphQLGetClient.getInstance(endpoint) logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) const query = gql` query { - getPublicInfo { + getPublicCommunityInfo { name description createdAt @@ -71,8 +73,8 @@ export class FederationClientImpl implements FederationClient { ) logger.debug(`Response-Data:`, data, errors, extensions, headers, status) if (data) { - logger.debug(`Response-PublicInfo:`, data.getPublicInfo.publicInfo) - logger.info(`requestGetPublicInfo processed successfully`) + logger.debug(`Response-PublicCommunityInfo:`, data.getPublicInfo.publicInfo) + logger.info(`requestGetPublicCommunityInfo processed successfully`) return data.getPublicInfo.publicInfo } logger.warn(`requestGetPublicInfo processed without response data`) diff --git a/backend/src/federation/client/1_1/FederationClient.ts b/backend/src/federation/client/1_1/FederationClient.ts deleted file mode 100644 index 35c88bf3b..000000000 --- a/backend/src/federation/client/1_1/FederationClient.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { gql } from 'graphql-request' - -import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' -import { LogError } from '@/server/LogError' -import { backendLogger as logger } from '@/server/logger' - -export async function requestGetPublicKey( - dbCom: DbFederatedCommunity, -): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicKey { - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) - logger.info(`requestGetPublicKey processed successfully`) - return data.getPublicKey.publicKey - } - logger.warn(`requestGetPublicKey processed without response data`) - } catch (err) { - throw new LogError(`Request-Error:`, err) - } -} diff --git a/backend/src/federation/client/1_1/FederationClientImpl.ts b/backend/src/federation/client/1_1/FederationClientImpl.ts index 472edcdbd..14153723d 100644 --- a/backend/src/federation/client/1_1/FederationClientImpl.ts +++ b/backend/src/federation/client/1_1/FederationClientImpl.ts @@ -9,7 +9,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' // eslint-disable-next-line import/no-relative-parent-imports -import { FederationClient, PublicInfo } from '../FederationClient' +import { FederationClient, PublicCommunityInfo } from '../FederationClient' export class FederationClientImpl implements FederationClient { async requestGetPublicKey(dbCom: DbFederatedCommunity): Promise { @@ -45,16 +45,18 @@ export class FederationClientImpl implements FederationClient { } } - async requestGetPublicInfo(dbCom: DbFederatedCommunity): Promise { + async requestGetPublicCommunityInfo( + dbCom: DbFederatedCommunity, + ): Promise { let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicInfo with endpoint='${endpoint}'...`) + logger.info(`requestGetPublicCommunityInfo with endpoint='${endpoint}'...`) const graphQLClient = GraphQLGetClient.getInstance(endpoint) logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) const query = gql` query { - getPublicInfo { + getPublicCommunityInfo { name description createdAt @@ -71,9 +73,12 @@ export class FederationClientImpl implements FederationClient { ) logger.debug(`Response-Data:`, data, errors, extensions, headers, status) if (data) { - logger.debug(`Response-PublicInfo:`, data.getPublicInfo.publicInfo) + logger.debug( + `Response-PublicCommunityInfo:`, + data.getPublicCommunityInfo.publicCommunityInfo, + ) logger.info(`requestGetPublicInfo processed successfully`) - return data.getPublicInfo.publicInfo + return data.getPublicCommunityInfo.publicCommunityInfo } logger.warn(`requestGetPublicInfo processed without response data`) } catch (err) { diff --git a/backend/src/federation/client/FederationClient.ts b/backend/src/federation/client/FederationClient.ts index 3ddd80cfb..2af13e002 100644 --- a/backend/src/federation/client/FederationClient.ts +++ b/backend/src/federation/client/FederationClient.ts @@ -1,6 +1,6 @@ import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -export type PublicInfo = { +export type PublicCommunityInfo = { name: string description: string createdAt: Date @@ -9,5 +9,7 @@ export type PublicInfo = { export interface FederationClient { requestGetPublicKey(dbCom: DbFederatedCommunity): Promise - requestGetPublicInfo(dbCom: DbFederatedCommunity): Promise + requestGetPublicCommunityInfo( + dbCom: DbFederatedCommunity, + ): Promise } diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index da8484318..1338279fd 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -11,7 +11,7 @@ import { backendLogger as logger } from '@/server/logger' import { FederationClientImpl as V1_0_FederationClientImpl } from './client/1_0/FederationClientImpl' // eslint-disable-next-line camelcase import { FederationClientImpl as V1_1_FederationClientImpl } from './client/1_1/FederationClientImpl' -import { FederationClient, PublicInfo } from './client/FederationClient' +import { FederationClient, PublicCommunityInfo } from './client/FederationClient' import { ApiVersionType } from './enum/apiVersionType' export function startValidateCommunities(timerInterval: number): void { @@ -58,9 +58,9 @@ export async function validateCommunities(): Promise { logger.debug(`Federation: updated dbCom: ${JSON.stringify(dbCom)}`) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const pubInfo = await getVersionedFederationClient(dbCom.apiVersion).requestGetPublicInfo( - dbCom, - ) + const pubInfo = await getVersionedFederationClient( + dbCom.apiVersion, + ).requestGetPublicCommunityInfo(dbCom) logger.debug(`Federation: getPublicInfo pubInfo: ${JSON.stringify(pubInfo)}`) if (pubInfo) { logger.info(`Federation: write foreign community...`) @@ -91,7 +91,7 @@ export async function validateCommunities(): Promise { async function writeForeignCommunity( dbCom: DbFederatedCommunity, - pubInfo: PublicInfo, + pubInfo: PublicCommunityInfo, ): Promise { if (dbCom && pubInfo) { const foreignCom = DbCommunity.create() diff --git a/federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts similarity index 93% rename from federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts rename to federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts index 102f446ba..bb36cdcda 100644 --- a/federation/src/graphql/api/1_0/model/GetPublicInfoResult.ts +++ b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts @@ -4,7 +4,7 @@ import { Field, ObjectType } from 'type-graphql' @ObjectType() // eslint-disable-next-line @typescript-eslint/no-unused-vars -export class GetPublicInfoResult { +export class GetPublicCommunityInfoResult { constructor(dbCom: DbCommunity) { this.publicKey = dbCom.publicKey.toString('hex') this.name = dbCom.name diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts new file mode 100644 index 000000000..31287c494 --- /dev/null +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts @@ -0,0 +1,18 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Query, Resolver } from 'type-graphql' +import { federationLogger as logger } from '@/server/logger' +import { Community as DbCommunity } from '@entity/Community' +import { GetPublicCommunityInfoResult } from '../model/GetPublicCommunityInfoResult' + +@Resolver() +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export class PublicCommunityInfoResolver { + @Query(() => GetPublicCommunityInfoResult) + async getPublicCommunityInfo(): Promise { + logger.debug(`getPublicCommunityInfo() via apiVersion=1_0 ...`) + const homeCom = await DbCommunity.findOneOrFail({ foreign: false }) + const result = new GetPublicCommunityInfoResult(homeCom) + logger.info(`getPublicCommunityInfo()-1_0... return publicInfo=${result}`) + return result + } +} diff --git a/federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts b/federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts deleted file mode 100644 index ad988670b..000000000 --- a/federation/src/graphql/api/1_0/resolver/PublicInfoResolver.ts +++ /dev/null @@ -1,18 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Query, Resolver } from 'type-graphql' -import { federationLogger as logger } from '@/server/logger' -import { Community as DbCommunity } from '@entity/Community' -import { GetPublicInfoResult } from '../model/GetPublicInfoResult' - -@Resolver() -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export class PublicInfoResolver { - @Query(() => GetPublicInfoResult) - async getPublicInfo(): Promise { - logger.debug(`getPublicInfo() via apiVersion=1_0 ...`) - const homeCom = await DbCommunity.findOneOrFail({ foreign: false }) - const result = new GetPublicInfoResult(homeCom) - logger.info(`getPublicInfo()-1_0... return publicInfo=${result}`) - return result - } -} From ac09e641027f6778560ac53e19b67374dca94fbc Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 4 May 2023 18:15:47 +0200 Subject: [PATCH 03/49] change getPublicInfo to getPublicCommunityInfo --- backend/src/federation/client/1_0/FederationClientImpl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/federation/client/1_0/FederationClientImpl.ts b/backend/src/federation/client/1_0/FederationClientImpl.ts index 4bd194594..543653ea7 100644 --- a/backend/src/federation/client/1_0/FederationClientImpl.ts +++ b/backend/src/federation/client/1_0/FederationClientImpl.ts @@ -73,9 +73,9 @@ export class FederationClientImpl implements FederationClient { ) logger.debug(`Response-Data:`, data, errors, extensions, headers, status) if (data) { - logger.debug(`Response-PublicCommunityInfo:`, data.getPublicInfo.publicInfo) + logger.debug(`Response-PublicCommunityInfo:`, data.getPublicCommunityInfo) logger.info(`requestGetPublicCommunityInfo processed successfully`) - return data.getPublicInfo.publicInfo + return data.getPublicCommunityInfo } logger.warn(`requestGetPublicInfo processed without response data`) } catch (err) { From 85c78b3e36973893f1fa6d5dd278d2cf2aabcb96 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 5 May 2023 18:11:35 +0200 Subject: [PATCH 04/49] correct writeForeignCommunity --- backend/src/federation/validateCommunities.ts | 35 ++++++++++--------- .../resolver/PublicCommunityInfoResolver.ts | 6 +++- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index c50e4b827..eee0261f5 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -90,23 +90,24 @@ async function writeForeignCommunity( dbCom: DbFederatedCommunity, pubInfo: PublicCommunityInfo, ): Promise { - const variables = { - public_key: pubInfo.publicKey, - url: dbCom.endPoint, - name: pubInfo.name, - description: pubInfo.description, - creation_date: pubInfo.createdAt, - } - if (dbCom && pubInfo) { - await DbCommunity.createQueryBuilder() - .insert() - .into(DbCommunity) - .values(variables) - .orUpdate({ - conflict_target: ['id', 'public_key'], - overwrite: ['url', 'name', 'description', 'creation_date'], - }) - .execute() + if (!dbCom || !pubInfo || !(dbCom.publicKey.toString('hex') === pubInfo.publicKey)) { + logger.error( + `Error in writeForeignCommunity: missmatching parameters or publicKey. pubInfo:${JSON.stringify( + pubInfo, + )}`, + ) + } else { + let com = await DbCommunity.findOne({ publicKey: dbCom.publicKey }) + if (!com) { + com = DbCommunity.create() + } + com.creationDate = pubInfo.createdAt + com.description = pubInfo.description + com.foreign = true + com.name = pubInfo.name + com.publicKey = dbCom.publicKey + com.url = dbCom.endPoint + await DbCommunity.save(com) } } diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts index 31287c494..3bd02e346 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts @@ -12,7 +12,11 @@ export class PublicCommunityInfoResolver { logger.debug(`getPublicCommunityInfo() via apiVersion=1_0 ...`) const homeCom = await DbCommunity.findOneOrFail({ foreign: false }) const result = new GetPublicCommunityInfoResult(homeCom) - logger.info(`getPublicCommunityInfo()-1_0... return publicInfo=${result}`) + logger.info( + `getPublicCommunityInfo()-1_0... return publicInfo=${JSON.stringify( + result + )}` + ) return result } } From 20c8b7573157b4cf367dd0c04b3862dbc75b0827 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 5 May 2023 21:01:56 +0200 Subject: [PATCH 05/49] not working linting even on using suggestion --- .../src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts index bb36cdcda..ea225203a 100644 --- a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts +++ b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts @@ -1,5 +1,6 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Community as DbCommunity } from '@entity/Community' +/* @typescript-eslint/no-unused-vars */ import { Field, ObjectType } from 'type-graphql' @ObjectType() From 54173857ce61860620b881328032548d0794fab1 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 16 May 2023 01:14:00 +0200 Subject: [PATCH 06/49] adapt client refactorings, v1_1 not matching --- .../client/1_0/FederationClientImpl.ts | 91 ------------------ .../client/1_1/FederationClientImpl.ts | 94 ------------------- backend/src/federation/client/Client_1_1.ts | 44 ++++++++- .../src/federation/client/FederationClient.ts | 15 --- .../query/getPublicCommunityInfo.ts | 12 +++ 5 files changed, 55 insertions(+), 201 deletions(-) delete mode 100644 backend/src/federation/client/1_0/FederationClientImpl.ts delete mode 100644 backend/src/federation/client/1_1/FederationClientImpl.ts delete mode 100644 backend/src/federation/client/FederationClient.ts create mode 100644 backend/src/federation/query/getPublicCommunityInfo.ts diff --git a/backend/src/federation/client/1_0/FederationClientImpl.ts b/backend/src/federation/client/1_0/FederationClientImpl.ts deleted file mode 100644 index 8dfbd17af..000000000 --- a/backend/src/federation/client/1_0/FederationClientImpl.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { GraphQLError } from 'graphql/error/GraphQLError' -import { gql } from 'graphql-request' - -import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' -import { backendLogger as logger } from '@/server/logger' - -// eslint-disable-next-line import/no-relative-parent-imports -import { FederationClient, PublicCommunityInfo } from '../FederationClient' - -export class FederationClientImpl implements FederationClient { - public async requestGetPublicKey(dbCom: DbFederatedCommunity): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicKey { - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) - logger.info(`requestGetPublicKey processed successfully`) - return data.getPublicKey.publicKey - } - logger.warn(`requestGetPublicKey processed without response data`) - } catch (err) { - if (err instanceof GraphQLError) { - logger.error(`RawRequest-Error on {} with message {}`, endpoint, err.message) - } - throw new Error(`Request-Error in requestGetPublicKey.`) - } - } - - public async requestGetPublicCommunityInfo( - dbCom: DbFederatedCommunity, - ): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicCommunityInfo with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicCommunityInfo { - name - description - createdAt - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicCommunityInfo:`, data.getPublicCommunityInfo) - logger.info(`requestGetPublicCommunityInfo processed successfully`) - return data.getPublicCommunityInfo - } - logger.warn(`requestGetPublicInfo processed without response data`) - } catch (err) { - if (err instanceof GraphQLError) { - logger.error(`RawRequest-Error on {} with message {}`, endpoint, err.message) - } - throw new Error(`Request-Error in requestGetPublicCommunityInfo.`) - } - } -} diff --git a/backend/src/federation/client/1_1/FederationClientImpl.ts b/backend/src/federation/client/1_1/FederationClientImpl.ts deleted file mode 100644 index affc9a984..000000000 --- a/backend/src/federation/client/1_1/FederationClientImpl.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { GraphQLError } from 'graphql/error/GraphQLError' -import { gql } from 'graphql-request' - -import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' -import { backendLogger as logger } from '@/server/logger' - -// eslint-disable-next-line import/no-relative-parent-imports -import { FederationClient, PublicCommunityInfo } from '../FederationClient' - -export class FederationClientImpl implements FederationClient { - async requestGetPublicKey(dbCom: DbFederatedCommunity): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicKey { - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) - logger.info(`requestGetPublicKey processed successfully`) - return data.getPublicKey.publicKey - } - logger.warn(`requestGetPublicKey processed without response data`) - } catch (err) { - if (err instanceof GraphQLError) { - logger.error(`RawRequest-Error on {} with message {}`, endpoint, err.message) - } - throw new Error(`Request-Error in requestGetPublicKey.`) - } - } - - async requestGetPublicCommunityInfo( - dbCom: DbFederatedCommunity, - ): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicCommunityInfo with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicCommunityInfo { - name - description - createdAt - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug( - `Response-PublicCommunityInfo:`, - data.getPublicCommunityInfo.publicCommunityInfo, - ) - logger.info(`requestGetPublicInfo processed successfully`) - return data.getPublicCommunityInfo.publicCommunityInfo - } - logger.warn(`requestGetPublicInfo processed without response data`) - } catch (err) { - if (err instanceof GraphQLError) { - logger.error(`RawRequest-Error on {} with message {}`, endpoint, err.message) - } - throw new Error(`Request-Error in requestGetPublicCommunityInfo.`) - } - } -} diff --git a/backend/src/federation/client/Client_1_1.ts b/backend/src/federation/client/Client_1_1.ts index 8525acc5d..7ef17694e 100644 --- a/backend/src/federation/client/Client_1_1.ts +++ b/backend/src/federation/client/Client_1_1.ts @@ -1,5 +1,47 @@ +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' + +import { getPublicCommunityInfo } from '@/federation/query/getPublicCommunityInfo' +import { backendLogger as logger } from '@/server/logger' + // eslint-disable-next-line camelcase import { Client_1_0 } from './Client_1_0' +export interface PublicCommunityInfo { + name: string + description: string + createdAt: Date + publicKey: string +} + // eslint-disable-next-line camelcase -export class Client_1_1 extends Client_1_0 {} +export class Client_1_1 extends Client_1_0 { + constructor(dbCom: DbFederatedCommunity) { + super(dbCom) + } + + getPublicCommunityInfo = async (): Promise => { + logger.info(`getPublicCommunityInfo with endpoint='${this.endpoint}'...`) + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = await this.client.rawRequest(getPublicCommunityInfo, {}) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (!data?.getPublicCommunityInfo?.name) { + logger.warn( + 'Federation: getPublicCommunityInfo without response data from endpoint', + this.endpoint, + ) + return + } + logger.info( + 'Federation: getPublicCommunityInfo successful from endpoint', + this.endpoint, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + data.getPublicCommunityInfo, + ) + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access + return data.getPublicCommunityInfo + } catch (err) { + logger.warn('Federation: getPublicCommunityInfo failed for endpoint', this.endpoint) + } + } +} diff --git a/backend/src/federation/client/FederationClient.ts b/backend/src/federation/client/FederationClient.ts deleted file mode 100644 index 2af13e002..000000000 --- a/backend/src/federation/client/FederationClient.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' - -export type PublicCommunityInfo = { - name: string - description: string - createdAt: Date - publicKey: string -} - -export interface FederationClient { - requestGetPublicKey(dbCom: DbFederatedCommunity): Promise - requestGetPublicCommunityInfo( - dbCom: DbFederatedCommunity, - ): Promise -} diff --git a/backend/src/federation/query/getPublicCommunityInfo.ts b/backend/src/federation/query/getPublicCommunityInfo.ts new file mode 100644 index 000000000..4dead00bd --- /dev/null +++ b/backend/src/federation/query/getPublicCommunityInfo.ts @@ -0,0 +1,12 @@ +import { gql } from 'graphql-request' + +export const getPublicCommunityInfo = gql` + query { + getPublicCommunityInfo { + name + description + createdAt + publicKey + } + } +` From 6ec82faa108ff71ada673f1b5c3d76dea6d9ccf3 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 19 May 2023 14:45:28 +0200 Subject: [PATCH 07/49] changes after refactoring problems, eslint shifted in issue #2997 --- backend/src/federation/client/Client_1_1.ts | 7 ------- backend/src/federation/validateCommunities.ts | 12 +++++++----- .../api/1_0/model/GetPublicCommunityInfoResult.ts | 2 +- federation/tsconfig.json | 2 +- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/backend/src/federation/client/Client_1_1.ts b/backend/src/federation/client/Client_1_1.ts index 7ef17694e..9a67757f4 100644 --- a/backend/src/federation/client/Client_1_1.ts +++ b/backend/src/federation/client/Client_1_1.ts @@ -1,5 +1,3 @@ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' - import { getPublicCommunityInfo } from '@/federation/query/getPublicCommunityInfo' import { backendLogger as logger } from '@/server/logger' @@ -13,12 +11,7 @@ export interface PublicCommunityInfo { publicKey: string } -// eslint-disable-next-line camelcase export class Client_1_1 extends Client_1_0 { - constructor(dbCom: DbFederatedCommunity) { - super(dbCom) - } - getPublicCommunityInfo = async (): Promise => { logger.info(`getPublicCommunityInfo with endpoint='${this.endpoint}'...`) try { diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index f00f4b4b3..adca47f77 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -7,7 +7,7 @@ import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCom import { backendLogger as logger } from '@/server/logger' import { Client } from './client/Client' -import { PublicCommunityInfo } from './client/Client_1_1' +import { PublicCommunityInfo, Client_1_1 } from './client/Client_1_1' import { ApiVersionType } from './enum/apiVersionType' export function startValidateCommunities(timerInterval: number): void { @@ -44,10 +44,12 @@ export async function validateCommunities(): Promise { if (pubKey && pubKey === dbCom.publicKey.toString()) { await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) logger.info('Federation: verified community', dbCom) - const pubComInfo = await client?.getPublicCommunityInfo() - if (pubComInfo) { - await writeForeignCommunity(dbCom, pubComInfo) - logger.info(`Federation: write foreign community... successfully`) + if (client instanceof Client_1_1) { + const pubComInfo = await client.getPublicCommunityInfo() + if (pubComInfo) { + await writeForeignCommunity(dbCom, pubComInfo) + logger.info(`Federation: write foreign community... successfully`) + } } } else { logger.warn( diff --git a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts index ea225203a..91ed5e2ff 100644 --- a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts +++ b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Community as DbCommunity } from '@entity/Community' -/* @typescript-eslint/no-unused-vars */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { Field, ObjectType } from 'type-graphql' @ObjectType() diff --git a/federation/tsconfig.json b/federation/tsconfig.json index b38c43ba1..2326786ac 100644 --- a/federation/tsconfig.json +++ b/federation/tsconfig.json @@ -4,7 +4,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "target": "esNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ From 3e22b0780697a2ac2858637d0da3ddcacc1c386a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 19 May 2023 17:19:21 +0200 Subject: [PATCH 08/49] optimize log-output --- backend/src/federation/client/1_0/FederationClient.ts | 11 ++++------- backend/src/federation/validateCommunities.ts | 10 +++------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/backend/src/federation/client/1_0/FederationClient.ts b/backend/src/federation/client/1_0/FederationClient.ts index 69e67efcb..170b4e954 100644 --- a/backend/src/federation/client/1_0/FederationClient.ts +++ b/backend/src/federation/client/1_0/FederationClient.ts @@ -51,7 +51,7 @@ export class FederationClient { } getPublicCommunityInfo = async (): Promise => { - logger.info(`getPublicCommunityInfo with endpoint='${this.endpoint}'...`) + logger.info(`Federation: getPublicCommunityInfo with endpoint='${this.endpoint}'...`) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(getPublicCommunityInfo, {}) @@ -63,12 +63,9 @@ export class FederationClient { ) return } - logger.info( - 'Federation: getPublicCommunityInfo successful from endpoint', - this.endpoint, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.getPublicCommunityInfo, - ) + logger.info(`Federation: getPublicCommunityInfo successful from endpoint=${this.endpoint}`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + logger.debug(`publicCommunityInfo:`, data.getPublicCommunityInfo) // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return data.getPublicCommunityInfo } catch (err) { diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 9fcbdda23..6bb020687 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -47,18 +47,14 @@ export async function validateCommunities(): Promise { const pubKey = await client.getPublicKey() if (pubKey && pubKey === dbCom.publicKey.toString()) { await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) - logger.info('Federation: verified community', dbCom) + logger.info(`Federation: verified community: id=${dbCom.id}, endpoint=${dbCom.endPoint}`) const pubComInfo = await client.getPublicCommunityInfo() if (pubComInfo) { await writeForeignCommunity(dbCom, pubComInfo) - logger.info(`Federation: write foreign community... successfully`) + logger.info(`Federation: write publicInfo of community: name=${pubComInfo.name}`) } } else { - logger.warn( - 'Federation: received not matching publicKey:', - pubKey, - dbCom.publicKey.toString(), - ) + logger.warn(`Federation: received none or not matching publicKey...`) } } } catch (err) { From b4fc67574ec4cc1a8fb50c398164e0b83d2cef48 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 23 May 2023 01:48:30 +0200 Subject: [PATCH 09/49] first try to extent tests --- .../federation/validateCommunities.test.ts | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index 0da5914bd..b1f8d0abe 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -87,7 +87,37 @@ describe('validate Communities', () => { overwrite: ['end_point', 'last_announced_at'], }) .execute() - + /* + // eslint-disable-next-line @typescript-eslint/require-await + jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + data: { + getPublicCommunityInfo: { + name: 'Test-Community', + description: 'Description of Test-Community', + createdAt: 'someDate', + publicKey: 'somePubKey', + }, + }, + } as Response + }) + const variables2 = { + publicKey: Buffer.from('11111111111111111111111111111111'), + apiVersion: '1_0', + endPoint: 'http//localhost:5001/api/', + lastAnnouncedAt: new Date(), + } + await DbCommunity.createQueryBuilder() + .insert() + .into(DbFederatedCommunity) + .values(variables1) + .orUpdate({ + conflict_target: ['id', 'publicKey', 'apiVersion'], + overwrite: ['end_point', 'last_announced_at'], + }) + .execute() + */ jest.clearAllMocks() await validateCommunities() }) @@ -102,11 +132,7 @@ describe('validate Communities', () => { ) }) it('logs not matching publicKeys', () => { - expect(logger.warn).toBeCalledWith( - 'Federation: received not matching publicKey:', - 'somePubKey', - expect.stringMatching('11111111111111111111111111111111'), - ) + expect(logger.warn).toBeCalledWith('Federation: received none or not matching publicKey...') }) }) describe('with two Communities of api 1_0 and 1_1', () => { From c3772224c4b20a19f02f0beac1c10281259acc7a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 25 May 2023 02:26:00 +0200 Subject: [PATCH 10/49] additional test with missing response data --- .../federation/validateCommunities.test.ts | 116 +++++++++++++++++- backend/src/federation/validateCommunities.ts | 8 +- 2 files changed, 116 insertions(+), 8 deletions(-) diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index c489798bb..c23bb84c2 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -59,7 +59,46 @@ describe('validate Communities', () => { expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`) }) - describe('with one Community of api 1_0', () => { + describe('with one Community of api 1_0 but missing pubKey response', () => { + beforeEach(async () => { + // eslint-disable-next-line @typescript-eslint/require-await + jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { data: {} } as Response }) + const variables1 = { + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), + apiVersion: '1_0', + endPoint: 'http//localhost:5001/api/', + lastAnnouncedAt: new Date(), + } + await DbFederatedCommunity.createQueryBuilder() + .insert() + .into(DbFederatedCommunity) + .values(variables1) + .orUpdate({ + // eslint-disable-next-line camelcase + conflict_target: ['id', 'publicKey', 'apiVersion'], + overwrite: ['end_point', 'last_announced_at'], + }) + .execute() + jest.clearAllMocks() + await validateCommunities() + }) + + it('logs one community found', () => { + expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`) + }) + it('logs requestGetPublicKey missing response data ', () => { + expect(logger.warn).toBeCalledWith( + 'Federation: getPublicKey without response data from endpoint', + 'http//localhost:5001/api/1_0/', + ) + }) + }) + + describe('with one Community of api 1_0 and not matching pubKey', () => { beforeEach(async () => { // eslint-disable-next-line @typescript-eslint/require-await jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => { @@ -73,7 +112,9 @@ describe('validate Communities', () => { } as Response }) const variables1 = { - publicKey: Buffer.from('11111111111111111111111111111111'), + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), apiVersion: '1_0', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -133,7 +174,64 @@ describe('validate Communities', () => { ) }) it('logs not matching publicKeys', () => { - expect(logger.warn).toBeCalledWith('Federation: received none or not matching publicKey...') + expect(logger.warn).toBeCalledWith( + 'Federation: received not matching publicKey:', + 'somePubKey', + expect.stringMatching('1111111111111111111111111111111111111111111111111111111111111111'), + ) + }) + }) + describe('with one Community of api 1_0 and matching pubKey', () => { + beforeEach(async () => { + // eslint-disable-next-line @typescript-eslint/require-await + jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + data: { + getPublicKey: { + publicKey: '1111111111111111111111111111111111111111111111111111111111111111', + }, + }, + } as Response + }) + const variables1 = { + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), + apiVersion: '1_0', + endPoint: 'http//localhost:5001/api/', + lastAnnouncedAt: new Date(), + } + await DbFederatedCommunity.createQueryBuilder() + .insert() + .into(DbFederatedCommunity) + .values(variables1) + .orUpdate({ + // eslint-disable-next-line camelcase + conflict_target: ['id', 'publicKey', 'apiVersion'], + overwrite: ['end_point', 'last_announced_at'], + }) + .execute() + await DbFederatedCommunity.update({}, { verifiedAt: null }) + jest.clearAllMocks() + await validateCommunities() + }) + + it('logs one community found', () => { + expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`) + }) + it('logs requestGetPublicKey for community api 1_0 ', () => { + expect(logger.info).toBeCalledWith( + 'Federation: getPublicKey from endpoint', + 'http//localhost:5001/api/1_0/', + ) + }) + it('logs community pubKey verified', () => { + expect(logger.info).toHaveBeenNthCalledWith( + 3, + 'Federation: verified community with:', + 'http//localhost:5001/api/', + ) }) }) describe('with two Communities of api 1_0 and 1_1', () => { @@ -145,13 +243,15 @@ describe('validate Communities', () => { return { data: { getPublicKey: { - publicKey: '11111111111111111111111111111111', + publicKey: '1111111111111111111111111111111111111111111111111111111111111111', }, }, } as Response }) const variables2 = { - publicKey: Buffer.from('11111111111111111111111111111111'), + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), apiVersion: '1_1', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -167,6 +267,7 @@ describe('validate Communities', () => { }) .execute() + await DbFederatedCommunity.update({}, { verifiedAt: null }) jest.clearAllMocks() await validateCommunities() }) @@ -190,7 +291,9 @@ describe('validate Communities', () => { let dbCom: DbFederatedCommunity beforeEach(async () => { const variables3 = { - publicKey: Buffer.from('11111111111111111111111111111111'), + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), apiVersion: '2_0', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -208,6 +311,7 @@ describe('validate Communities', () => { dbCom = await DbFederatedCommunity.findOneOrFail({ where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion }, }) + await DbFederatedCommunity.update({}, { verifiedAt: null }) jest.clearAllMocks() await validateCommunities() }) diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 6bb020687..3cf30195a 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -47,14 +47,18 @@ export async function validateCommunities(): Promise { const pubKey = await client.getPublicKey() if (pubKey && pubKey === dbCom.publicKey.toString()) { await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) - logger.info(`Federation: verified community: id=${dbCom.id}, endpoint=${dbCom.endPoint}`) + logger.info(`Federation: verified community with:`, dbCom.endPoint) const pubComInfo = await client.getPublicCommunityInfo() if (pubComInfo) { await writeForeignCommunity(dbCom, pubComInfo) logger.info(`Federation: write publicInfo of community: name=${pubComInfo.name}`) } } else { - logger.warn(`Federation: received none or not matching publicKey...`) + logger.warn( + 'Federation: received not matching publicKey:', + pubKey, + dbCom.publicKey.toString(), + ) } } } catch (err) { From 7c4d55928fa3a1eeeaecbc5f3f26aaa23deb11db Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 9 Aug 2023 13:29:41 +0200 Subject: [PATCH 11/49] move memo length validation into args object like it is recommended for typed graphql --- .../src/graphql/arg/AdminCreateContributionArgs.ts | 5 +++++ .../src/graphql/arg/AdminUpdateContributionArgs.ts | 5 +++++ backend/src/graphql/arg/ContributionArgs.ts | 5 +++++ backend/src/graphql/arg/ContributionLinkArgs.ts | 5 +++++ backend/src/graphql/arg/TransactionLinkArgs.ts | 5 +++++ backend/src/graphql/arg/TransactionSendArgs.ts | 5 +++++ .../graphql/resolver/ContributionLinkResolver.ts | 13 +------------ .../src/graphql/resolver/ContributionResolver.ts | 13 ------------- backend/src/graphql/resolver/TransactionResolver.ts | 9 --------- 9 files changed, 31 insertions(+), 34 deletions(-) diff --git a/backend/src/graphql/arg/AdminCreateContributionArgs.ts b/backend/src/graphql/arg/AdminCreateContributionArgs.ts index 8e2fa28da..1c6007c48 100644 --- a/backend/src/graphql/arg/AdminCreateContributionArgs.ts +++ b/backend/src/graphql/arg/AdminCreateContributionArgs.ts @@ -1,6 +1,9 @@ +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, InputType } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' + @InputType() @ArgsType() export class AdminCreateContributionArgs { @@ -11,6 +14,8 @@ export class AdminCreateContributionArgs { amount: Decimal @Field(() => String) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) memo: string @Field(() => String) diff --git a/backend/src/graphql/arg/AdminUpdateContributionArgs.ts b/backend/src/graphql/arg/AdminUpdateContributionArgs.ts index e79260c63..4958b4346 100644 --- a/backend/src/graphql/arg/AdminUpdateContributionArgs.ts +++ b/backend/src/graphql/arg/AdminUpdateContributionArgs.ts @@ -1,6 +1,9 @@ +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, Int } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' + @ArgsType() export class AdminUpdateContributionArgs { @Field(() => Int) @@ -10,6 +13,8 @@ export class AdminUpdateContributionArgs { amount: Decimal @Field(() => String) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) memo: string @Field(() => String) diff --git a/backend/src/graphql/arg/ContributionArgs.ts b/backend/src/graphql/arg/ContributionArgs.ts index db688d811..e2449ea95 100644 --- a/backend/src/graphql/arg/ContributionArgs.ts +++ b/backend/src/graphql/arg/ContributionArgs.ts @@ -1,6 +1,9 @@ +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, InputType } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' + @InputType() @ArgsType() export class ContributionArgs { @@ -8,6 +11,8 @@ export class ContributionArgs { amount: Decimal @Field(() => String) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) memo: string @Field(() => String) diff --git a/backend/src/graphql/arg/ContributionLinkArgs.ts b/backend/src/graphql/arg/ContributionLinkArgs.ts index cef72148a..9d12a6128 100644 --- a/backend/src/graphql/arg/ContributionLinkArgs.ts +++ b/backend/src/graphql/arg/ContributionLinkArgs.ts @@ -1,6 +1,9 @@ +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, Int } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' + @ArgsType() export class ContributionLinkArgs { @Field(() => Decimal) @@ -10,6 +13,8 @@ export class ContributionLinkArgs { name: string @Field(() => String) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) memo: string @Field(() => String) diff --git a/backend/src/graphql/arg/TransactionLinkArgs.ts b/backend/src/graphql/arg/TransactionLinkArgs.ts index f44ef0356..2ffd91d33 100644 --- a/backend/src/graphql/arg/TransactionLinkArgs.ts +++ b/backend/src/graphql/arg/TransactionLinkArgs.ts @@ -1,11 +1,16 @@ +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' + @ArgsType() export class TransactionLinkArgs { @Field(() => Decimal) amount: Decimal @Field(() => String) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) memo: string } diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index ecda848d1..1279f2051 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -1,6 +1,9 @@ +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' + @ArgsType() export class TransactionSendArgs { @Field(() => String) @@ -10,5 +13,7 @@ export class TransactionSendArgs { amount: Decimal @Field(() => String) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) memo: string } diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.ts b/backend/src/graphql/resolver/ContributionLinkResolver.ts index 808bd584e..42593dec1 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.ts @@ -18,12 +18,7 @@ import { import { Context, getUser } from '@/server/context' import { LogError } from '@/server/LogError' -import { - CONTRIBUTIONLINK_NAME_MAX_CHARS, - CONTRIBUTIONLINK_NAME_MIN_CHARS, - MEMO_MAX_CHARS, - MEMO_MIN_CHARS, -} from './const/const' +import { CONTRIBUTIONLINK_NAME_MAX_CHARS, CONTRIBUTIONLINK_NAME_MIN_CHARS } from './const/const' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' import { isStartEndDateValid } from './util/creations' @@ -52,12 +47,6 @@ export class ContributionLinkResolver { if (name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS) { throw new LogError('The value of name is too long', name.length) } - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('The value of memo is too short', memo.length) - } - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('The value of memo is too long', memo.length) - } if (!new Decimal(amount).isPositive()) { throw new LogError('The amount must be a positiv value', amount) } diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 80ea3e783..7b4c21708 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -45,7 +45,6 @@ import { calculateDecay } from '@/util/decay' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { fullName } from '@/util/utilities' -import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { getUserCreation, validateContribution, @@ -65,12 +64,6 @@ export class ContributionResolver { @Ctx() context: Context, ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('Memo text is too short', memo.length) - } - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('Memo text is too long', memo.length) - } const user = getUser(context) const creations = await getUserCreation(user.id, clientTimezoneOffset) @@ -186,12 +179,6 @@ export class ContributionResolver { @Ctx() context: Context, ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('Memo text is too short', memo.length) - } - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('Memo text is too long', memo.length) - } const user = getUser(context) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 5f42fb36e..721cba8fa 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -33,7 +33,6 @@ import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { BalanceResolver } from './BalanceResolver' -import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' @@ -55,14 +54,6 @@ export const executeTransaction = async ( throw new LogError('Sender and Recipient are the same', sender.id) } - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('Memo text is too short', memo.length) - } - - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('Memo text is too long', memo.length) - } - // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance( From 51d1c6b64bf95ce7fc388c8870f10fba0437bb39 Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 9 Aug 2023 13:55:46 +0200 Subject: [PATCH 12/49] add logging for automatic validation --- backend/src/graphql/schema.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index f14276c86..98af37159 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -1,9 +1,12 @@ import path from 'path' +import { validate } from 'class-validator' import { Decimal } from 'decimal.js-light' import { GraphQLSchema } from 'graphql' import { buildSchema } from 'type-graphql' +import { LogError } from '@/server/LogError' + import { isAuthorized } from './directive/isAuthorized' import { DecimalScalar } from './scalar/Decimal' @@ -12,5 +15,20 @@ export const schema = async (): Promise => { resolvers: [path.join(__dirname, 'resolver', `!(*.test).{js,ts}`)], authChecker: isAuthorized, scalarsMap: [{ type: Decimal, scalar: DecimalScalar }], + validate: (argValue) => { + if (argValue) { + validate(argValue) + .then((errors) => { + if (errors.length > 0) { + throw new LogError('validation failed. errors: ', errors) + } else { + return true + } + }) + .catch((e) => { + throw new LogError('validation throw an exception: ', e) + }) + } + }, }) } From 191103a812a4e9b8dbc0e764dbd3153d65860dec Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 9 Aug 2023 13:59:29 +0200 Subject: [PATCH 13/49] move CONTRIBUTIONLINK_NAME validation into arg --- backend/src/graphql/arg/ContributionLinkArgs.ts | 9 ++++++++- backend/src/graphql/resolver/ContributionLinkResolver.ts | 8 +------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/src/graphql/arg/ContributionLinkArgs.ts b/backend/src/graphql/arg/ContributionLinkArgs.ts index 9d12a6128..59688a969 100644 --- a/backend/src/graphql/arg/ContributionLinkArgs.ts +++ b/backend/src/graphql/arg/ContributionLinkArgs.ts @@ -2,7 +2,12 @@ import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, Int } from 'type-graphql' -import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { + MEMO_MAX_CHARS, + MEMO_MIN_CHARS, + CONTRIBUTIONLINK_NAME_MIN_CHARS, + CONTRIBUTIONLINK_NAME_MAX_CHARS, +} from '@/graphql/resolver/const/const' @ArgsType() export class ContributionLinkArgs { @@ -10,6 +15,8 @@ export class ContributionLinkArgs { amount: Decimal @Field(() => String) + @MaxLength(CONTRIBUTIONLINK_NAME_MAX_CHARS) + @MinLength(CONTRIBUTIONLINK_NAME_MIN_CHARS) name: string @Field(() => String) diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.ts b/backend/src/graphql/resolver/ContributionLinkResolver.ts index 42593dec1..e8a694a55 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.ts @@ -18,7 +18,6 @@ import { import { Context, getUser } from '@/server/context' import { LogError } from '@/server/LogError' -import { CONTRIBUTIONLINK_NAME_MAX_CHARS, CONTRIBUTIONLINK_NAME_MIN_CHARS } from './const/const' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' import { isStartEndDateValid } from './util/creations' @@ -41,12 +40,7 @@ export class ContributionLinkResolver { @Ctx() context: Context, ): Promise { isStartEndDateValid(validFrom, validTo) - if (name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS) { - throw new LogError('The value of name is too short', name.length) - } - if (name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS) { - throw new LogError('The value of name is too long', name.length) - } + if (!new Decimal(amount).isPositive()) { throw new LogError('The amount must be a positiv value', amount) } From 5e3d732e20eb6d0dbde98dda491e0d55c189e676 Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 9 Aug 2023 14:08:29 +0200 Subject: [PATCH 14/49] add validator for positive amount value with Decimal Type --- .../arg/AdminCreateContributionArgs.ts | 2 ++ .../arg/AdminUpdateContributionArgs.ts | 2 ++ backend/src/graphql/arg/ContributionArgs.ts | 2 ++ .../src/graphql/arg/ContributionLinkArgs.ts | 2 ++ .../src/graphql/arg/TransactionLinkArgs.ts | 4 +++- .../src/graphql/arg/TransactionSendArgs.ts | 2 ++ .../resolver/ContributionLinkResolver.ts | 4 ---- .../resolver/TransactionLinkResolver.ts | 4 ---- .../graphql/resolver/TransactionResolver.ts | 3 --- backend/src/graphql/validator/Decimal.ts | 22 +++++++++++++++++++ 10 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 backend/src/graphql/validator/Decimal.ts diff --git a/backend/src/graphql/arg/AdminCreateContributionArgs.ts b/backend/src/graphql/arg/AdminCreateContributionArgs.ts index 1c6007c48..558821921 100644 --- a/backend/src/graphql/arg/AdminCreateContributionArgs.ts +++ b/backend/src/graphql/arg/AdminCreateContributionArgs.ts @@ -3,6 +3,7 @@ import { Decimal } from 'decimal.js-light' import { ArgsType, Field, InputType } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @InputType() @ArgsType() @@ -11,6 +12,7 @@ export class AdminCreateContributionArgs { email: string @Field(() => Decimal) + @IsPositiveDecimal() amount: Decimal @Field(() => String) diff --git a/backend/src/graphql/arg/AdminUpdateContributionArgs.ts b/backend/src/graphql/arg/AdminUpdateContributionArgs.ts index 4958b4346..c5f560c9a 100644 --- a/backend/src/graphql/arg/AdminUpdateContributionArgs.ts +++ b/backend/src/graphql/arg/AdminUpdateContributionArgs.ts @@ -3,6 +3,7 @@ import { Decimal } from 'decimal.js-light' import { ArgsType, Field, Int } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() export class AdminUpdateContributionArgs { @@ -10,6 +11,7 @@ export class AdminUpdateContributionArgs { id: number @Field(() => Decimal) + @IsPositiveDecimal() amount: Decimal @Field(() => String) diff --git a/backend/src/graphql/arg/ContributionArgs.ts b/backend/src/graphql/arg/ContributionArgs.ts index e2449ea95..d4c9e639b 100644 --- a/backend/src/graphql/arg/ContributionArgs.ts +++ b/backend/src/graphql/arg/ContributionArgs.ts @@ -3,11 +3,13 @@ import { Decimal } from 'decimal.js-light' import { ArgsType, Field, InputType } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @InputType() @ArgsType() export class ContributionArgs { @Field(() => Decimal) + @IsPositiveDecimal() amount: Decimal @Field(() => String) diff --git a/backend/src/graphql/arg/ContributionLinkArgs.ts b/backend/src/graphql/arg/ContributionLinkArgs.ts index 59688a969..4aa268d28 100644 --- a/backend/src/graphql/arg/ContributionLinkArgs.ts +++ b/backend/src/graphql/arg/ContributionLinkArgs.ts @@ -8,10 +8,12 @@ import { CONTRIBUTIONLINK_NAME_MIN_CHARS, CONTRIBUTIONLINK_NAME_MAX_CHARS, } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() export class ContributionLinkArgs { @Field(() => Decimal) + @IsPositiveDecimal() amount: Decimal @Field(() => String) diff --git a/backend/src/graphql/arg/TransactionLinkArgs.ts b/backend/src/graphql/arg/TransactionLinkArgs.ts index 2ffd91d33..6dfdda15e 100644 --- a/backend/src/graphql/arg/TransactionLinkArgs.ts +++ b/backend/src/graphql/arg/TransactionLinkArgs.ts @@ -1,12 +1,14 @@ -import { MaxLength, MinLength } from 'class-validator' +import { IsAlpha, MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() export class TransactionLinkArgs { @Field(() => Decimal) + @IsPositiveDecimal() amount: Decimal @Field(() => String) diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index 1279f2051..d8a556b99 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -3,6 +3,7 @@ import { Decimal } from 'decimal.js-light' import { ArgsType, Field } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() export class TransactionSendArgs { @@ -10,6 +11,7 @@ export class TransactionSendArgs { identifier: string @Field(() => Decimal) + @IsPositiveDecimal() amount: Decimal @Field(() => String) diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.ts b/backend/src/graphql/resolver/ContributionLinkResolver.ts index e8a694a55..1f9e5ce26 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.ts @@ -41,10 +41,6 @@ export class ContributionLinkResolver { ): Promise { isStartEndDateValid(validFrom, validTo) - if (!new Decimal(amount).isPositive()) { - throw new LogError('The amount must be a positiv value', amount) - } - const dbContributionLink = new DbContributionLink() dbContributionLink.amount = amount dbContributionLink.name = name diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 282e6662a..cc0484588 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -73,10 +73,6 @@ export class TransactionLinkResolver { const createdDate = new Date() const validUntil = transactionLinkExpireDate(createdDate) - if (amount.lessThanOrEqualTo(0)) { - throw new LogError('Amount must be a positive number', amount) - } - const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay) // validate amount diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 721cba8fa..525be81fa 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -308,9 +308,6 @@ export class TransactionResolver { @Ctx() context: Context, ): Promise { logger.info(`sendCoins(identifier=${identifier}, amount=${amount}, memo=${memo})`) - if (amount.lte(0)) { - throw new LogError('Amount to send must be positive', amount) - } const senderUser = getUser(context) diff --git a/backend/src/graphql/validator/Decimal.ts b/backend/src/graphql/validator/Decimal.ts new file mode 100644 index 000000000..caf13143d --- /dev/null +++ b/backend/src/graphql/validator/Decimal.ts @@ -0,0 +1,22 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator' +import { Decimal } from 'decimal.js-light' + +export function IsPositiveDecimal(validationOptions?: ValidationOptions) { + return function (object: NonNullable, propertyName: string) { + registerDecorator({ + name: 'isPositiveDecimal', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + target: object.constructor, + propertyName, + options: validationOptions, + validator: { + validate(value: Decimal) { + return value.greaterThan(0) + }, + defaultMessage(args: ValidationArguments) { + return `The ${propertyName} must be a positive value ${args.property}` + }, + }, + }) + } +} From 6d6c520f63d58401ead752341e6277e930a8021f Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 9 Aug 2023 14:22:02 +0200 Subject: [PATCH 15/49] remove not longer needed import --- backend/src/graphql/resolver/ContributionLinkResolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.ts b/backend/src/graphql/resolver/ContributionLinkResolver.ts index 1f9e5ce26..0a04d387d 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.ts @@ -1,6 +1,5 @@ import { MoreThan, IsNull } from '@dbTools/typeorm' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' -import { Decimal } from 'decimal.js-light' import { Resolver, Args, Arg, Authorized, Mutation, Query, Int, Ctx } from 'type-graphql' import { ContributionLinkArgs } from '@arg/ContributionLinkArgs' From 60b0eed582894b4a2049e6124ec1cf4ff5cc6449 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Wed, 9 Aug 2023 14:55:21 +0200 Subject: [PATCH 16/49] fix problem with decorator --- backend/src/graphql/validator/Decimal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/validator/Decimal.ts b/backend/src/graphql/validator/Decimal.ts index caf13143d..09e8fb4bd 100644 --- a/backend/src/graphql/validator/Decimal.ts +++ b/backend/src/graphql/validator/Decimal.ts @@ -2,10 +2,10 @@ import { registerDecorator, ValidationOptions, ValidationArguments } from 'class import { Decimal } from 'decimal.js-light' export function IsPositiveDecimal(validationOptions?: ValidationOptions) { - return function (object: NonNullable, propertyName: string) { + // eslint-disable-next-line @typescript-eslint/ban-types + return function (object: Object, propertyName: string) { registerDecorator({ name: 'isPositiveDecimal', - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment target: object.constructor, propertyName, options: validationOptions, From e2f65cddc3079c28b5cc29927630d089e63bad39 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Fri, 11 Aug 2023 10:39:53 +0200 Subject: [PATCH 17/49] increase auto validation, update tests --- .../arg/AdminCreateContributionArgs.ts | 5 +- .../arg/AdminUpdateContributionArgs.ts | 5 +- backend/src/graphql/arg/ContributionArgs.ts | 2 + .../src/graphql/arg/ContributionLinkArgs.ts | 8 +- .../graphql/arg/ContributionMessageArgs.ts | 4 + backend/src/graphql/arg/CreateUserArgs.ts | 8 + backend/src/graphql/arg/Paginated.ts | 4 + backend/src/graphql/arg/SearchUsersFilters.ts | 3 + backend/src/graphql/arg/SetUserRoleArgs.ts | 3 + .../src/graphql/arg/TransactionLinkArgs.ts | 2 +- .../src/graphql/arg/TransactionLinkFilters.ts | 4 + .../src/graphql/arg/TransactionSendArgs.ts | 3 +- backend/src/graphql/arg/UnsecureLoginArgs.ts | 4 + .../src/graphql/arg/UpdateUserInfosArgs.ts | 10 + .../resolver/ContributionLinkResolver.test.ts | 195 +++++++++++------- .../resolver/ContributionResolver.test.ts | 158 ++++++++++---- .../graphql/resolver/ContributionResolver.ts | 4 - .../resolver/TransactionLinkResolver.test.ts | 74 ++++--- .../resolver/TransactionResolver.test.ts | 131 +++++++----- backend/src/graphql/schema.ts | 24 +-- backend/src/graphql/validator/Alias.ts | 0 backend/src/graphql/validator/DateString.ts | 21 ++ 22 files changed, 439 insertions(+), 233 deletions(-) create mode 100644 backend/src/graphql/validator/Alias.ts create mode 100644 backend/src/graphql/validator/DateString.ts diff --git a/backend/src/graphql/arg/AdminCreateContributionArgs.ts b/backend/src/graphql/arg/AdminCreateContributionArgs.ts index 558821921..e734fc514 100644 --- a/backend/src/graphql/arg/AdminCreateContributionArgs.ts +++ b/backend/src/graphql/arg/AdminCreateContributionArgs.ts @@ -1,14 +1,16 @@ -import { MaxLength, MinLength } from 'class-validator' +import { IsEmail, MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, InputType } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { isValidDateString } from '@/graphql/validator/DateString' import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @InputType() @ArgsType() export class AdminCreateContributionArgs { @Field(() => String) + @IsEmail() email: string @Field(() => Decimal) @@ -21,5 +23,6 @@ export class AdminCreateContributionArgs { memo: string @Field(() => String) + @isValidDateString() creationDate: string } diff --git a/backend/src/graphql/arg/AdminUpdateContributionArgs.ts b/backend/src/graphql/arg/AdminUpdateContributionArgs.ts index c5f560c9a..f7288cb28 100644 --- a/backend/src/graphql/arg/AdminUpdateContributionArgs.ts +++ b/backend/src/graphql/arg/AdminUpdateContributionArgs.ts @@ -1,13 +1,15 @@ -import { MaxLength, MinLength } from 'class-validator' +import { IsPositive, MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, Int } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { isValidDateString } from '@/graphql/validator/DateString' import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() export class AdminUpdateContributionArgs { @Field(() => Int) + @IsPositive() id: number @Field(() => Decimal) @@ -20,5 +22,6 @@ export class AdminUpdateContributionArgs { memo: string @Field(() => String) + @isValidDateString() creationDate: string } diff --git a/backend/src/graphql/arg/ContributionArgs.ts b/backend/src/graphql/arg/ContributionArgs.ts index d4c9e639b..9f3951ac2 100644 --- a/backend/src/graphql/arg/ContributionArgs.ts +++ b/backend/src/graphql/arg/ContributionArgs.ts @@ -3,6 +3,7 @@ import { Decimal } from 'decimal.js-light' import { ArgsType, Field, InputType } from 'type-graphql' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { isValidDateString } from '@/graphql/validator/DateString' import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @InputType() @@ -18,5 +19,6 @@ export class ContributionArgs { memo: string @Field(() => String) + @isValidDateString() creationDate: string } diff --git a/backend/src/graphql/arg/ContributionLinkArgs.ts b/backend/src/graphql/arg/ContributionLinkArgs.ts index 4aa268d28..97cf3dfdd 100644 --- a/backend/src/graphql/arg/ContributionLinkArgs.ts +++ b/backend/src/graphql/arg/ContributionLinkArgs.ts @@ -1,4 +1,4 @@ -import { MaxLength, MinLength } from 'class-validator' +import { IsPositive, IsString, MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field, Int } from 'type-graphql' @@ -8,6 +8,7 @@ import { CONTRIBUTIONLINK_NAME_MIN_CHARS, CONTRIBUTIONLINK_NAME_MAX_CHARS, } from '@/graphql/resolver/const/const' +import { isValidDateString } from '@/graphql/validator/DateString' import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() @@ -27,17 +28,22 @@ export class ContributionLinkArgs { memo: string @Field(() => String) + @IsString() cycle: string @Field(() => String, { nullable: true }) + @isValidDateString() validFrom?: string | null @Field(() => String, { nullable: true }) + @isValidDateString() validTo?: string | null @Field(() => Decimal, { nullable: true }) + @IsPositiveDecimal() maxAmountPerMonth?: Decimal | null @Field(() => Int) + @IsPositive() maxPerCycle: number } diff --git a/backend/src/graphql/arg/ContributionMessageArgs.ts b/backend/src/graphql/arg/ContributionMessageArgs.ts index 6482793aa..847cb5b33 100644 --- a/backend/src/graphql/arg/ContributionMessageArgs.ts +++ b/backend/src/graphql/arg/ContributionMessageArgs.ts @@ -1,3 +1,4 @@ +import { IsInt, IsString, IsEnum } from 'class-validator' import { ArgsType, Field, Int, InputType } from 'type-graphql' import { ContributionMessageType } from '@enum/ContributionMessageType' @@ -6,11 +7,14 @@ import { ContributionMessageType } from '@enum/ContributionMessageType' @ArgsType() export class ContributionMessageArgs { @Field(() => Int) + @IsInt() contributionId: number @Field(() => String) + @IsString() message: string @Field(() => ContributionMessageType, { defaultValue: ContributionMessageType.DIALOG }) + @IsEnum(ContributionMessageType) messageType: ContributionMessageType } diff --git a/backend/src/graphql/arg/CreateUserArgs.ts b/backend/src/graphql/arg/CreateUserArgs.ts index c348b9b4c..28105559d 100644 --- a/backend/src/graphql/arg/CreateUserArgs.ts +++ b/backend/src/graphql/arg/CreateUserArgs.ts @@ -1,25 +1,33 @@ +import { IsEmail, IsInt, IsString } from 'class-validator' import { ArgsType, Field, Int } from 'type-graphql' @ArgsType() export class CreateUserArgs { @Field(() => String, { nullable: true }) + @IsString() alias?: string | null @Field(() => String) + @IsEmail() email: string @Field(() => String) + @IsString() firstName: string @Field(() => String) + @IsString() lastName: string @Field(() => String, { nullable: true }) + @IsString() language?: string | null @Field(() => Int, { nullable: true }) + @IsInt() publisherId?: number | null @Field(() => String, { nullable: true }) + @IsString() redeemCode?: string | null } diff --git a/backend/src/graphql/arg/Paginated.ts b/backend/src/graphql/arg/Paginated.ts index a1e792ef7..c63416e7e 100644 --- a/backend/src/graphql/arg/Paginated.ts +++ b/backend/src/graphql/arg/Paginated.ts @@ -1,4 +1,5 @@ /* eslint-disable type-graphql/invalid-nullable-input-type */ +import { IsPositive, IsEnum } from 'class-validator' import { ArgsType, Field, Int } from 'type-graphql' import { Order } from '@enum/Order' @@ -6,11 +7,14 @@ import { Order } from '@enum/Order' @ArgsType() export class Paginated { @Field(() => Int, { nullable: true }) + @IsPositive() currentPage?: number @Field(() => Int, { nullable: true }) + @IsPositive() pageSize?: number @Field(() => Order, { nullable: true }) + @IsEnum(Order) order?: Order } diff --git a/backend/src/graphql/arg/SearchUsersFilters.ts b/backend/src/graphql/arg/SearchUsersFilters.ts index a6ea09268..d82500aea 100644 --- a/backend/src/graphql/arg/SearchUsersFilters.ts +++ b/backend/src/graphql/arg/SearchUsersFilters.ts @@ -1,10 +1,13 @@ +import { IsBoolean } from 'class-validator' import { Field, InputType } from 'type-graphql' @InputType() export class SearchUsersFilters { @Field(() => Boolean, { nullable: true, defaultValue: null }) + @IsBoolean() byActivated?: boolean | null @Field(() => Boolean, { nullable: true, defaultValue: null }) + @IsBoolean() byDeleted?: boolean | null } diff --git a/backend/src/graphql/arg/SetUserRoleArgs.ts b/backend/src/graphql/arg/SetUserRoleArgs.ts index c076fc8cf..039d190cd 100644 --- a/backend/src/graphql/arg/SetUserRoleArgs.ts +++ b/backend/src/graphql/arg/SetUserRoleArgs.ts @@ -1,3 +1,4 @@ +import { IsPositive, IsEnum } from 'class-validator' import { ArgsType, Field, Int, InputType } from 'type-graphql' import { RoleNames } from '@enum/RoleNames' @@ -6,8 +7,10 @@ import { RoleNames } from '@enum/RoleNames' @ArgsType() export class SetUserRoleArgs { @Field(() => Int) + @IsPositive() userId: number @Field(() => RoleNames, { nullable: true }) + @IsEnum(RoleNames) role: RoleNames | null | undefined } diff --git a/backend/src/graphql/arg/TransactionLinkArgs.ts b/backend/src/graphql/arg/TransactionLinkArgs.ts index 6dfdda15e..039686bca 100644 --- a/backend/src/graphql/arg/TransactionLinkArgs.ts +++ b/backend/src/graphql/arg/TransactionLinkArgs.ts @@ -1,4 +1,4 @@ -import { IsAlpha, MaxLength, MinLength } from 'class-validator' +import { MaxLength, MinLength } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field } from 'type-graphql' diff --git a/backend/src/graphql/arg/TransactionLinkFilters.ts b/backend/src/graphql/arg/TransactionLinkFilters.ts index de8643260..831a30834 100644 --- a/backend/src/graphql/arg/TransactionLinkFilters.ts +++ b/backend/src/graphql/arg/TransactionLinkFilters.ts @@ -1,14 +1,18 @@ /* eslint-disable type-graphql/invalid-nullable-input-type */ +import { IsBoolean } from 'class-validator' import { Field, InputType } from 'type-graphql' @InputType() export class TransactionLinkFilters { @Field(() => Boolean, { nullable: true }) + @IsBoolean() withDeleted?: boolean @Field(() => Boolean, { nullable: true }) + @IsBoolean() withExpired?: boolean @Field(() => Boolean, { nullable: true }) + @IsBoolean() withRedeemed?: boolean } diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index d8a556b99..026a87eef 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -1,4 +1,4 @@ -import { MaxLength, MinLength } from 'class-validator' +import { MaxLength, MinLength, IsString } from 'class-validator' import { Decimal } from 'decimal.js-light' import { ArgsType, Field } from 'type-graphql' @@ -8,6 +8,7 @@ import { IsPositiveDecimal } from '@/graphql/validator/Decimal' @ArgsType() export class TransactionSendArgs { @Field(() => String) + @IsString() identifier: string @Field(() => Decimal) diff --git a/backend/src/graphql/arg/UnsecureLoginArgs.ts b/backend/src/graphql/arg/UnsecureLoginArgs.ts index ad5a934f9..5e82f12f3 100644 --- a/backend/src/graphql/arg/UnsecureLoginArgs.ts +++ b/backend/src/graphql/arg/UnsecureLoginArgs.ts @@ -1,13 +1,17 @@ +import { IsEmail, IsInt, IsString } from 'class-validator' import { ArgsType, Field, Int } from 'type-graphql' @ArgsType() export class UnsecureLoginArgs { @Field(() => String) + @IsEmail() email: string @Field(() => String) + @IsString() password: string @Field(() => Int, { nullable: true }) + @IsInt() publisherId?: number | null } diff --git a/backend/src/graphql/arg/UpdateUserInfosArgs.ts b/backend/src/graphql/arg/UpdateUserInfosArgs.ts index e57d4ec82..6b2ab1032 100644 --- a/backend/src/graphql/arg/UpdateUserInfosArgs.ts +++ b/backend/src/graphql/arg/UpdateUserInfosArgs.ts @@ -1,31 +1,41 @@ +import { IsBoolean, IsInt, IsString } from 'class-validator' import { ArgsType, Field, Int } from 'type-graphql' @ArgsType() export class UpdateUserInfosArgs { @Field({ nullable: true }) + @IsString() firstName?: string @Field({ nullable: true }) + @IsString() lastName?: string @Field({ nullable: true }) + @IsString() alias?: string @Field({ nullable: true }) + @IsString() language?: string @Field(() => Int, { nullable: true }) + @IsInt() publisherId?: number | null @Field({ nullable: true }) + @IsString() password?: string @Field({ nullable: true }) + @IsString() passwordNew?: string @Field({ nullable: true }) + @IsBoolean() hideAmountGDD?: boolean @Field({ nullable: true }) + @IsBoolean() hideAmountGDT?: boolean } diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts index 9605378e2..23be529d3 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts @@ -339,107 +339,142 @@ describe('Contribution Links', () => { it('returns an error if name is shorter than 5 characters', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createContributionLink, - variables: { - ...variables, - name: '123', + const { errors: errorObjects } = await mutate({ + mutation: createContributionLink, + variables: { + ...variables, + name: '123', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'name', + constraints: { + minLength: 'name must be longer than or equal to 5 characters', + }, + }, + ], + }, }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('The value of name is too short')], - }), - ) - }) - - it('logs the error "The value of name is too short"', () => { - expect(logger.error).toBeCalledWith('The value of name is too short', 3) + }, + ]) }) it('returns an error if name is longer than 100 characters', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createContributionLink, - variables: { - ...variables, - name: '12345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901', + const { errors: errorObjects } = await mutate({ + mutation: createContributionLink, + variables: { + ...variables, + name: '12345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'name', + constraints: { + maxLength: 'name must be shorter than or equal to 100 characters', + }, + }, + ], + }, }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('The value of name is too long')], - }), - ) - }) - - it('logs the error "The value of name is too long"', () => { - expect(logger.error).toBeCalledWith('The value of name is too long', 101) + }, + ]) }) it('returns an error if memo is shorter than 5 characters', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createContributionLink, - variables: { - ...variables, - memo: '123', + const { errors: errorObjects } = await mutate({ + mutation: createContributionLink, + variables: { + ...variables, + memo: '123', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + minLength: 'memo must be longer than or equal to 5 characters', + }, + }, + ], + }, }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('The value of memo is too short')], - }), - ) - }) - - it('logs the error "The value of memo is too short"', () => { - expect(logger.error).toBeCalledWith('The value of memo is too short', 3) + }, + ]) }) it('returns an error if memo is longer than 255 characters', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createContributionLink, - variables: { - ...variables, - memo: '1234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456', + const { errors: errorObjects } = await mutate({ + mutation: createContributionLink, + variables: { + ...variables, + memo: '1234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + maxLength: 'memo must be shorter than or equal to 255 characters', + }, + }, + ], + }, }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('The value of memo is too long')], - }), - ) - }) - - it('logs the error "The value of memo is too long"', () => { - expect(logger.error).toBeCalledWith('The value of memo is too long', 256) + }, + ]) }) it('returns an error if amount is not positive', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createContributionLink, - variables: { - ...variables, - amount: new Decimal(0), + const { errors: errorObjects } = await mutate({ + mutation: createContributionLink, + variables: { + ...variables, + amount: new Decimal(0), + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'amount', + constraints: { + isPositiveDecimal: 'The amount must be a positive value amount', + }, + }, + ], + }, }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('The amount must be a positiv value')], - }), - ) - }) - - it('logs the error "The amount must be a positiv value"', () => { - expect(logger.error).toBeCalledWith('The amount must be a positiv value', new Decimal(0)) + }, + ]) }) }) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index cbb92eff8..167edfe74 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -201,6 +201,7 @@ describe('ContributionResolver', () => { it('throws error when memo length smaller than 5 chars', async () => { jest.clearAllMocks() const date = new Date() + const { errors: errorObjects } = await mutate({ mutation: createContribution, variables: { @@ -209,12 +210,23 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - - expect(errorObjects).toEqual([new GraphQLError('Memo text is too short')]) - }) - - it('logs the error "Memo text is too short"', () => { - expect(logger.error).toBeCalledWith('Memo text is too short', 4) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + minLength: 'memo must be longer than or equal to 5 characters', + }, + }, + ], + }, + }, + }, + ]) }) it('throws error when memo length greater than 255 chars', async () => { @@ -228,11 +240,23 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - expect(errorObjects).toEqual([new GraphQLError('Memo text is too long')]) - }) - - it('logs the error "Memo text is too long"', () => { - expect(logger.error).toBeCalledWith('Memo text is too long', 259) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + maxLength: 'memo must be shorter than or equal to 255 characters', + }, + }, + ], + }, + }, + }, + ]) }) it('throws error when creationDate not-valid', async () => { @@ -245,27 +269,35 @@ describe('ContributionResolver', () => { creationDate: 'not-valid', }, }) - expect(errorObjects).toEqual([ - new GraphQLError('No information for available creations for the given date'), + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'creationDate', + constraints: { + isValidDateString: 'creationDate must be a valid date string, creationDate', + }, + }, + ], + }, + }, + }, ]) }) - it('logs the error "No information for available creations for the given date"', () => { - expect(logger.error).toBeCalledWith( - 'No information for available creations for the given date', - expect.any(Date), - ) - }) - it('throws error when creationDate 3 month behind', async () => { jest.clearAllMocks() const date = new Date() + date.setMonth(date.getMonth() - 3) const { errors: errorObjects } = await mutate({ mutation: createContribution, variables: { amount: 100.0, memo: 'Test env contribution', - creationDate: date.setMonth(date.getMonth() - 3).toString(), + creationDate: date.toString(), }, }) expect(errorObjects).toEqual([ @@ -346,11 +378,23 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - expect(errorObjects).toEqual([new GraphQLError('Memo text is too short')]) - }) - - it('logs the error "Memo text is too short"', () => { - expect(logger.error).toBeCalledWith('Memo text is too short', 4) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + minLength: 'memo must be longer than or equal to 5 characters', + }, + }, + ], + }, + }, + }, + ]) }) }) @@ -367,11 +411,23 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - expect(errorObjects).toEqual([new GraphQLError('Memo text is too long')]) - }) - - it('logs the error "Memo text is too long"', () => { - expect(logger.error).toBeCalledWith('Memo text is too long', 259) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + maxLength: 'memo must be shorter than or equal to 255 characters', + }, + }, + ], + }, + }, + }, + ]) }) }) @@ -551,13 +607,14 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() const date = new Date() + date.setMonth(date.getMonth() - 3) const { errors: errorObjects } = await mutate({ mutation: updateContribution, variables: { contributionId: pendingContribution.data.createContribution.id, amount: 10.0, memo: 'Test env contribution', - creationDate: date.setMonth(date.getMonth() - 3).toString(), + creationDate: date.toString(), }, }) expect(errorObjects).toEqual([ @@ -1979,17 +2036,28 @@ describe('ContributionResolver', () => { describe('date of creation is not a date string', () => { it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ mutation: adminCreateContribution, variables }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('CreationDate is invalid')], - }), - ) - }) - - it('logs the error "CreationDate is invalid"', () => { - expect(logger.error).toBeCalledWith('CreationDate is invalid', 'invalid-date') + const { errors: errorObjects } = await mutate({ + mutation: adminCreateContribution, + variables, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'creationDate', + constraints: { + isValidDateString: + 'creationDate must be a valid date string, creationDate', + }, + }, + ], + }, + }, + }, + ]) }) }) @@ -2181,7 +2249,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminUpdateContribution, variables: { - id: -1, + id: 728, amount: new Decimal(300), memo: 'Danke Bibi!', creationDate: contributionDateFormatter(new Date()), @@ -2195,7 +2263,7 @@ describe('ContributionResolver', () => { }) it('logs the error "Contribution not found"', () => { - expect(logger.error).toBeCalledWith('Contribution not found', -1) + expect(logger.error).toBeCalledWith('Contribution not found', 728) }) }) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 7b4c21708..64206af2e 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -49,7 +49,6 @@ import { getUserCreation, validateContribution, updateCreations, - isValidDateString, getOpenCreations, } from './util/creations' import { findContributions } from './util/findContributions' @@ -256,9 +255,6 @@ export class ContributionResolver { `adminCreateContribution(email=${email}, amount=${amount.toString()}, memo=${memo}, creationDate=${creationDate})`, ) const clientTimezoneOffset = getClientTimezoneOffset(context) - if (!isValidDateString(creationDate)) { - throw new LogError('CreationDate is invalid', creationDate) - } const emailContact = await UserContact.findOne({ where: { email }, withDeleted: true, diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index ac76bdecf..59e8047c6 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -94,38 +94,58 @@ describe('TransactionLinkResolver', () => { it('throws error when amount is zero', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createTransactionLink, - variables: { - amount: 0, - memo: 'Test', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Amount must be a positive number')], + const { errors: errorObjects } = await mutate({ + mutation: createTransactionLink, + variables: { + amount: 0, + memo: 'Test Test', + }, }) - }) - it('logs the error "Amount must be a positive number" - 0', () => { - expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(0)) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'amount', + constraints: { + isPositiveDecimal: 'The amount must be a positive value amount', + }, + }, + ], + }, + }, + }, + ]) }) it('throws error when amount is negative', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createTransactionLink, - variables: { - amount: -10, - memo: 'Test', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Amount must be a positive number')], + const { errors: errorObjects } = await mutate({ + mutation: createTransactionLink, + variables: { + amount: -10, + memo: 'Test Test', + }, }) - }) - it('logs the error "Amount must be a positive number" - -10', () => { - expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(-10)) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'amount', + constraints: { + isPositiveDecimal: 'The amount must be a positive value amount', + }, + }, + ], + }, + }, + }, + ]) }) it('throws error when user has not enough GDD', async () => { @@ -135,7 +155,7 @@ describe('TransactionLinkResolver', () => { mutation: createTransactionLink, variables: { amount: 1001, - memo: 'Test', + memo: 'Test Test', }, }), ).resolves.toMatchObject({ diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 60445e239..e91efee6f 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -93,7 +93,7 @@ describe('send coins', () => { variables: { identifier: 'wrong@email.com', amount: 100, - memo: 'test', + memo: 'test test', }, }), ).toEqual( @@ -121,7 +121,7 @@ describe('send coins', () => { variables: { identifier: 'stephen@hawking.uk', amount: 100, - memo: 'test', + memo: 'test test', }, }), ).toEqual( @@ -150,7 +150,7 @@ describe('send coins', () => { variables: { identifier: 'garrick@ollivander.com', amount: 100, - memo: 'test', + memo: 'test test', }, }), ).toEqual( @@ -186,7 +186,7 @@ describe('send coins', () => { variables: { identifier: 'bob@baumeister.de', amount: 100, - memo: 'test', + memo: 'test test', }, }), ).toEqual( @@ -204,48 +204,62 @@ describe('send coins', () => { describe('memo text is too short', () => { it('throws an error', async () => { jest.clearAllMocks() - expect( - await mutate({ - mutation: sendCoins, - variables: { - identifier: 'peter@lustig.de', - amount: 100, - memo: 'test', + const { errors: errorObjects } = await mutate({ + mutation: sendCoins, + variables: { + identifier: 'peter@lustig.de', + amount: 100, + memo: 'Test', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + minLength: 'memo must be longer than or equal to 5 characters', + }, + }, + ], + }, }, - }), - ).toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Memo text is too short')], - }), - ) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Memo text is too short', 4) + }, + ]) }) }) describe('memo text is too long', () => { it('throws an error', async () => { jest.clearAllMocks() - expect( - await mutate({ - mutation: sendCoins, - variables: { - identifier: 'peter@lustig.de', - amount: 100, - memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t', + const { errors: errorObjects } = await mutate({ + mutation: sendCoins, + variables: { + identifier: 'peter@lustig.de', + amount: 100, + memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + maxLength: 'memo must be shorter than or equal to 255 characters', + }, + }, + ], + }, }, - }), - ).toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Memo text is too long')], - }), - ) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Memo text is too long', 256) + }, + ]) }) }) @@ -302,24 +316,31 @@ describe('send coins', () => { describe('trying to send negative amount', () => { it('throws an error', async () => { jest.clearAllMocks() - expect( - await mutate({ - mutation: sendCoins, - variables: { - identifier: 'peter@lustig.de', - amount: -50, - memo: 'testing negative', + const { errors: errorObjects } = await mutate({ + mutation: sendCoins, + variables: { + identifier: 'peter@lustig.de', + amount: -50, + memo: 'testing negative', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'amount', + constraints: { + isPositiveDecimal: 'The amount must be a positive value amount', + }, + }, + ], + }, }, - }), - ).toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Amount to send must be positive')], - }), - ) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Amount to send must be positive', new Decimal(-50)) + }, + ]) }) }) diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index 98af37159..878bb747c 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -1,12 +1,9 @@ import path from 'path' -import { validate } from 'class-validator' import { Decimal } from 'decimal.js-light' import { GraphQLSchema } from 'graphql' import { buildSchema } from 'type-graphql' -import { LogError } from '@/server/LogError' - import { isAuthorized } from './directive/isAuthorized' import { DecimalScalar } from './scalar/Decimal' @@ -15,20 +12,13 @@ export const schema = async (): Promise => { resolvers: [path.join(__dirname, 'resolver', `!(*.test).{js,ts}`)], authChecker: isAuthorized, scalarsMap: [{ type: Decimal, scalar: DecimalScalar }], - validate: (argValue) => { - if (argValue) { - validate(argValue) - .then((errors) => { - if (errors.length > 0) { - throw new LogError('validation failed. errors: ', errors) - } else { - return true - } - }) - .catch((e) => { - throw new LogError('validation throw an exception: ', e) - }) - } + validate: { + validationError: { target: false }, + skipMissingProperties: true, + skipNullProperties: true, + skipUndefinedProperties: true, + forbidUnknownValues: false, + stopAtFirstError: false, }, }) } diff --git a/backend/src/graphql/validator/Alias.ts b/backend/src/graphql/validator/Alias.ts new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/graphql/validator/DateString.ts b/backend/src/graphql/validator/DateString.ts new file mode 100644 index 000000000..4ee23b51a --- /dev/null +++ b/backend/src/graphql/validator/DateString.ts @@ -0,0 +1,21 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator' + +export function isValidDateString(validationOptions?: ValidationOptions) { + // eslint-disable-next-line @typescript-eslint/ban-types + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isValidDateString', + target: object.constructor, + propertyName, + options: validationOptions, + validator: { + validate(value: string) { + return new Date(value).toString() !== 'Invalid Date' + }, + defaultMessage(args: ValidationArguments) { + return `${propertyName} must be a valid date string, ${args.property}` + }, + }, + }) + } +} From a74d4697cf6c849ce3d4981247e76966321d53c4 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Fri, 11 Aug 2023 10:43:27 +0200 Subject: [PATCH 18/49] remove not longer needed import --- backend/src/graphql/resolver/TransactionResolver.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index e91efee6f..1957c680f 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -6,7 +6,6 @@ import { Event as DbEvent } from '@entity/Event' import { Transaction } from '@entity/Transaction' import { User } from '@entity/User' import { ApolloServerTestClient } from 'apollo-server-testing' -import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' From 8f6d2b5f3c46925f1f7eabeb233ef9cb80b55957 Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 16 Aug 2023 13:16:49 +0200 Subject: [PATCH 19/49] apply suggestions --- .../resolver/TransactionLinkResolver.test.ts | 57 +++++++++++++++++++ backend/src/graphql/schema.ts | 8 +-- backend/src/graphql/validator/Alias.ts | 0 3 files changed, 61 insertions(+), 4 deletions(-) delete mode 100644 backend/src/graphql/validator/Alias.ts diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 59e8047c6..fe047b35a 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -148,6 +148,63 @@ describe('TransactionLinkResolver', () => { ]) }) + it('throws error when memo text is too short', async () => { + jest.clearAllMocks() + const { errors: errorObjects } = await mutate({ + mutation: createTransactionLink, + variables: { + amount: 100, + memo: 'Test', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + minLength: 'memo must be longer than or equal to 5 characters', + }, + }, + ], + }, + }, + }, + ]) + }) + + it('throws error when memo text is too long', async () => { + jest.clearAllMocks() + const { errors: errorObjects } = await mutate({ + mutation: createTransactionLink, + variables: { + identifier: 'peter@lustig.de', + amount: 100, + memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t', + }, + }) + expect(errorObjects).toMatchObject([ + { + message: 'Argument Validation Error', + extensions: { + exception: { + validationErrors: [ + { + property: 'memo', + constraints: { + maxLength: 'memo must be shorter than or equal to 255 characters', + }, + }, + ], + }, + }, + }, + ]) + }) + it('throws error when user has not enough GDD', async () => { jest.clearAllMocks() await expect( diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index 878bb747c..a2e4ca71f 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -14,11 +14,11 @@ export const schema = async (): Promise => { scalarsMap: [{ type: Decimal, scalar: DecimalScalar }], validate: { validationError: { target: false }, - skipMissingProperties: true, + skipMissingProperties: false, skipNullProperties: true, - skipUndefinedProperties: true, - forbidUnknownValues: false, - stopAtFirstError: false, + skipUndefinedProperties: false, + forbidUnknownValues: true, + stopAtFirstError: true, }, }) } diff --git a/backend/src/graphql/validator/Alias.ts b/backend/src/graphql/validator/Alias.ts deleted file mode 100644 index e69de29bb..000000000 From 20fbdaf2ea17ae58988e4c8ba8591b57a8522907 Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Wed, 16 Aug 2023 13:46:08 +0200 Subject: [PATCH 20/49] change parameter to fix broken tests --- backend/src/graphql/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index a2e4ca71f..18214861f 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -14,7 +14,7 @@ export const schema = async (): Promise => { scalarsMap: [{ type: Decimal, scalar: DecimalScalar }], validate: { validationError: { target: false }, - skipMissingProperties: false, + skipMissingProperties: true, skipNullProperties: true, skipUndefinedProperties: false, forbidUnknownValues: true, From 72865bf9f257ec42c4eedc5158aa5ae61b625e10 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 17 Aug 2023 19:54:09 +0200 Subject: [PATCH 21/49] linting --- database/migrations/0068-community_tables_public_key_length.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/0068-community_tables_public_key_length.ts b/database/migrations/0068-community_tables_public_key_length.ts index 22c04e850..d5d047c26 100644 --- a/database/migrations/0068-community_tables_public_key_length.ts +++ b/database/migrations/0068-community_tables_public_key_length.ts @@ -39,4 +39,4 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom await queryFn( 'ALTER TABLE `communities` MODIFY COLUMN `public_key` binary(64) NULL DEFAULT NULL;', ) -} \ No newline at end of file +} From 986ac2c9986b4a7a8aab9036779eb68a28b10d7f Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 17 Aug 2023 23:58:27 +0200 Subject: [PATCH 22/49] additional testcases --- .../federation/validateCommunities.test.ts | 30 ++-- .../resolver/CommunityResolver.test.ts | 150 +++++++++++++++++- backend/src/seeds/graphql/queries.ts | 15 ++ 3 files changed, 184 insertions(+), 11 deletions(-) diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index 1b4f9f4a3..68d2433d8 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -67,9 +67,7 @@ describe('validate Communities', () => { return { data: {} } as Response }) const variables1 = { - publicKey: Buffer.from( - '1111111111111111111111111111111111111111111111111111111111111111', - ), + publicKey: Buffer.from('11111111111111111111111111111111'), apiVersion: '1_0', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -113,9 +111,7 @@ describe('validate Communities', () => { } as Response }) const variables1 = { - publicKey: Buffer.from( - '1111111111111111111111111111111111111111111111111111111111111111', - ), + publicKey: Buffer.from('11111111111111111111111111111111'), apiVersion: '1_0', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -227,10 +223,26 @@ describe('validate Communities', () => { }) it('logs community pubKey verified', () => { expect(logger.debug).toHaveBeenNthCalledWith( - 6, - 'Federation: verified community with', - 'http//localhost:5001/api/', + 5, + 'Federation: getPublicKey successful from endpoint', + 'http//localhost:5001/api/1_0/', + '11111111111111111111111111111111', ) + /* + await expect(DbCommunity.find()).resolves.toContainEqual( + expect.objectContaining({ + foreign: false, + url: 'http://localhost/api', + publicKey: Buffer.from('11111111111111111111111111111111'), + privateKey: expect.any(Buffer), + communityUuid: expect.any(String), + authenticatedAt: expect.any(Date), + name: expect.any(String), + description: expect.any(String), + creationDate: expect.any(Date), + }), + ) + */ }) }) describe('with two Communities of api 1_0 and 1_1', () => { diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts index 840544e51..4b4101e66 100644 --- a/backend/src/graphql/resolver/CommunityResolver.test.ts +++ b/backend/src/graphql/resolver/CommunityResolver.test.ts @@ -6,12 +6,13 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Connection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { ApolloServerTestClient } from 'apollo-server-testing' -import { testEnvironment } from '@test/helpers' +import { cleanDB, testEnvironment } from '@test/helpers' -import { getCommunities } from '@/seeds/graphql/queries' +import { getCommunities, getCommunitySelections } from '@/seeds/graphql/queries' // to do: We need a setup for the tests that closes the connection let query: ApolloServerTestClient['query'], con: Connection @@ -29,6 +30,7 @@ beforeAll(async () => { }) afterAll(async () => { + await cleanDB() await con.close() }) @@ -55,6 +57,7 @@ describe('CommunityResolver', () => { describe('only home-communities entries', () => { beforeEach(async () => { + await cleanDB() jest.clearAllMocks() homeCom1 = DbFederatedCommunity.create() @@ -230,4 +233,147 @@ describe('CommunityResolver', () => { }) }) }) + + describe('getCommunitySelections', () => { + let homeCom1: DbCommunity + let foreignCom1: DbCommunity + let foreignCom2: DbCommunity + + describe('with empty list', () => { + beforeEach(async () => { + await cleanDB() + jest.clearAllMocks() + }) + + it('returns no community entry', async () => { + // const result: Community[] = await query({ query: getCommunities }) + // expect(result.length).toEqual(0) + await expect(query({ query: getCommunitySelections })).resolves.toMatchObject({ + data: { + getCommunitySelections: [], + }, + }) + }) + }) + + describe('with one home-community entry', () => { + beforeEach(async () => { + await cleanDB() + jest.clearAllMocks() + + homeCom1 = DbCommunity.create() + homeCom1.foreign = false + homeCom1.url = 'http://localhost/api' + homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity') + homeCom1.privateKey = Buffer.from('privateKey-HomeCommunity') + homeCom1.communityUuid = 'HomeCom-UUID' + homeCom1.authenticatedAt = new Date() + homeCom1.name = 'HomeCommunity-name' + homeCom1.description = 'HomeCommunity-description' + homeCom1.creationDate = new Date() + await DbCommunity.insert(homeCom1) + }) + + it('returns 1 home-community entry', async () => { + await expect(query({ query: getCommunitySelections })).resolves.toMatchObject({ + data: { + getCommunitySelections: [ + { + id: expect.any(Number), + foreign: homeCom1.foreign, + name: homeCom1.name, + description: homeCom1.description, + url: homeCom1.url, + creationDate: homeCom1.creationDate?.toISOString(), + uuid: homeCom1.communityUuid, + authenticatedAt: homeCom1.authenticatedAt?.toISOString(), + }, + ], + }, + }) + }) + }) + + describe('with several community entries', () => { + beforeEach(async () => { + await cleanDB() + jest.clearAllMocks() + + homeCom1 = DbCommunity.create() + homeCom1.foreign = false + homeCom1.url = 'http://localhost/api' + homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity') + homeCom1.privateKey = Buffer.from('privateKey-HomeCommunity') + homeCom1.communityUuid = 'HomeCom-UUID' + homeCom1.authenticatedAt = new Date() + homeCom1.name = 'HomeCommunity-name' + homeCom1.description = 'HomeCommunity-description' + homeCom1.creationDate = new Date() + await DbCommunity.insert(homeCom1) + + foreignCom1 = DbCommunity.create() + foreignCom1.foreign = true + foreignCom1.url = 'http://stage-2.gradido.net/api' + foreignCom1.publicKey = Buffer.from('publicKey-stage-2_Community') + foreignCom1.privateKey = Buffer.from('privateKey-stage-2_Community') + foreignCom1.communityUuid = 'Stage2-Com-UUID' + foreignCom1.authenticatedAt = new Date() + foreignCom1.name = 'Stage-2_Community-name' + foreignCom1.description = 'Stage-2_Community-description' + foreignCom1.creationDate = new Date() + await DbCommunity.insert(foreignCom1) + + foreignCom2 = DbCommunity.create() + foreignCom2.foreign = true + foreignCom2.url = 'http://stage-3.gradido.net/api' + foreignCom2.publicKey = Buffer.from('publicKey-stage-3_Community') + foreignCom2.privateKey = Buffer.from('privateKey-stage-3_Community') + foreignCom2.communityUuid = 'Stage3-Com-UUID' + foreignCom2.authenticatedAt = new Date() + foreignCom2.name = 'Stage-3_Community-name' + foreignCom2.description = 'Stage-3_Community-description' + foreignCom2.creationDate = new Date() + await DbCommunity.insert(foreignCom2) + }) + + it('returns 3 community entries', async () => { + await expect(query({ query: getCommunitySelections })).resolves.toMatchObject({ + data: { + getCommunitySelections: [ + { + id: expect.any(Number), + foreign: homeCom1.foreign, + name: homeCom1.name, + description: homeCom1.description, + url: homeCom1.url, + creationDate: homeCom1.creationDate?.toISOString(), + uuid: homeCom1.communityUuid, + authenticatedAt: homeCom1.authenticatedAt?.toISOString(), + }, + { + id: expect.any(Number), + foreign: foreignCom1.foreign, + name: foreignCom1.name, + description: foreignCom1.description, + url: foreignCom1.url, + creationDate: foreignCom1.creationDate?.toISOString(), + uuid: foreignCom1.communityUuid, + authenticatedAt: foreignCom1.authenticatedAt?.toISOString(), + }, + { + id: expect.any(Number), + foreign: foreignCom2.foreign, + name: foreignCom2.name, + description: foreignCom2.description, + url: foreignCom2.url, + creationDate: foreignCom2.creationDate?.toISOString(), + uuid: foreignCom2.communityUuid, + authenticatedAt: foreignCom2.authenticatedAt?.toISOString(), + }, + ], + }, + }) + }) + }) + }) }) diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index f016102a2..3dda2633c 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -157,6 +157,21 @@ export const getCommunities = gql` } ` +export const getCommunitySelections = gql` + query { + getCommunitySelections { + id + foreign + name + description + url + creationDate + uuid + authenticatedAt + } + } +` + export const queryTransactionLink = gql` query ($code: String!) { queryTransactionLink(code: $code) { From b18dd8e636572334af9a82b9b7f7af044b11248d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 18 Aug 2023 01:09:27 +0200 Subject: [PATCH 23/49] additional Tests --- .../PublicCommunityInfoResolver.test.ts | 64 +++++++++++++++++++ .../resolver/PublicCommunityInfoResolver.ts | 8 +-- 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts new file mode 100644 index 000000000..08657c330 --- /dev/null +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -0,0 +1,64 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { createTestClient } from 'apollo-server-testing' +import createServer from '@/server/createServer' +import { Community as DbCommunity } from '@entity/Community' +import CONFIG from '@/config' + +let query: any + +// to do: We need a setup for the tests that closes the connection +let con: any + +CONFIG.FEDERATION_API = '1_0' + +beforeAll(async () => { + const server = await createServer() + con = server.con + query = createTestClient(server.apollo).query + DbCommunity.clear() +}) + +afterAll(async () => { + await con.close() +}) + +describe('PublicCommunityInfoResolver', () => { + const getPublicCommunityInfoQuery = ` + query { + getPublicCommunityInfo + { + name + description + createdAt + publicKey + } + } + ` + + describe('getPublicCommunityInfo', () => { + beforeEach(async () => { + const homeCom = new DbCommunity() + homeCom.foreign = false + homeCom.url = 'homeCommunity-url' + homeCom.name = 'Community-Name' + homeCom.description = 'Community-Description' + homeCom.createdAt = new Date() + homeCom.publicKey = Buffer.from('homeCommunity-publicKey') + await DbCommunity.insert(homeCom) + }) + + it('returns public CommunityInfo', async () => { + await expect(query({ query: getPublicCommunityInfoQuery })).resolves.toMatchObject({ + data: { + getPublicCommunityInfo: { + name: 'Community-Name', + description: 'Community-Description', + createdAt: expect.any(Date), + publicKey: expect.stringMatching('homeCommunity-publicKey'), + }, + }, + }) + }) + }) +}) diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts index 3bd02e346..3076edd41 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.ts @@ -10,13 +10,9 @@ export class PublicCommunityInfoResolver { @Query(() => GetPublicCommunityInfoResult) async getPublicCommunityInfo(): Promise { logger.debug(`getPublicCommunityInfo() via apiVersion=1_0 ...`) - const homeCom = await DbCommunity.findOneOrFail({ foreign: false }) + const homeCom = await DbCommunity.findOneByOrFail({ foreign: false }) const result = new GetPublicCommunityInfoResult(homeCom) - logger.info( - `getPublicCommunityInfo()-1_0... return publicInfo=${JSON.stringify( - result - )}` - ) + logger.info(`getPublicCommunityInfo()-1_0... return publicInfo=${JSON.stringify(result)}`) return result } } From ed1ace5aab5624a927046720d1b67669d324ed11 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 18 Aug 2023 17:31:39 +0200 Subject: [PATCH 24/49] add test and increase coverage --- .../api/1_0/model/GetPublicCommunityInfoResult.ts | 6 +++--- .../api/1_0/resolver/PublicCommunityInfoResolver.test.ts | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts index 91ed5e2ff..86ea480df 100644 --- a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts +++ b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts @@ -7,10 +7,10 @@ import { Field, ObjectType } from 'type-graphql' // eslint-disable-next-line @typescript-eslint/no-unused-vars export class GetPublicCommunityInfoResult { constructor(dbCom: DbCommunity) { - this.publicKey = dbCom.publicKey.toString('hex') + this.publicKey = dbCom.publicKey.toString() this.name = dbCom.name this.description = dbCom.description - this.createdAt = dbCom.creationDate + this.creationDate = dbCom.creationDate } @Field(() => String) @@ -20,7 +20,7 @@ export class GetPublicCommunityInfoResult { description: string | null @Field(() => Date) - createdAt: Date | null + creationDate: Date | null @Field(() => String) publicKey: string diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts index 08657c330..d18a30a7c 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -30,20 +30,21 @@ describe('PublicCommunityInfoResolver', () => { { name description - createdAt + creationDate publicKey } } ` describe('getPublicCommunityInfo', () => { + let homeCom: DbCommunity beforeEach(async () => { - const homeCom = new DbCommunity() + homeCom = new DbCommunity() homeCom.foreign = false homeCom.url = 'homeCommunity-url' homeCom.name = 'Community-Name' homeCom.description = 'Community-Description' - homeCom.createdAt = new Date() + homeCom.creationDate = new Date() homeCom.publicKey = Buffer.from('homeCommunity-publicKey') await DbCommunity.insert(homeCom) }) @@ -54,7 +55,7 @@ describe('PublicCommunityInfoResolver', () => { getPublicCommunityInfo: { name: 'Community-Name', description: 'Community-Description', - createdAt: expect.any(Date), + creationDate: homeCom.creationDate?.toISOString(), publicKey: expect.stringMatching('homeCommunity-publicKey'), }, }, From e4cadd6ae4a56380ac9d7d8601bfd09482791cc1 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 18 Aug 2023 17:53:58 +0200 Subject: [PATCH 25/49] increase coverage --- federation/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federation/jest.config.js b/federation/jest.config.js index f055d66e2..25ff58fb3 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -6,7 +6,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 72, + lines: 76, }, }, setupFiles: ['/test/testSetup.ts'], From a5dc9a8fc876a23b172a66775ad8ce7bc5135448 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 18 Aug 2023 17:54:41 +0200 Subject: [PATCH 26/49] switch logoutputs to debug level --- backend/src/federation/client/1_0/FederationClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/federation/client/1_0/FederationClient.ts b/backend/src/federation/client/1_0/FederationClient.ts index 7890461c9..8d915c751 100644 --- a/backend/src/federation/client/1_0/FederationClient.ts +++ b/backend/src/federation/client/1_0/FederationClient.ts @@ -51,7 +51,7 @@ export class FederationClient { } getPublicCommunityInfo = async (): Promise => { - logger.info(`Federation: getPublicCommunityInfo with endpoint='${this.endpoint}'...`) + logger.debug(`Federation: getPublicCommunityInfo with endpoint='${this.endpoint}'...`) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(getPublicCommunityInfo, {}) @@ -63,7 +63,7 @@ export class FederationClient { ) return } - logger.info(`Federation: getPublicCommunityInfo successful from endpoint=${this.endpoint}`) + logger.debug(`Federation: getPublicCommunityInfo successful from endpoint=${this.endpoint}`) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access logger.debug(`publicCommunityInfo:`, data.getPublicCommunityInfo) // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access From e00719714e1d271d675add56606115d435a98fac Mon Sep 17 00:00:00 2001 From: Einhornimmond Date: Fri, 18 Aug 2023 18:42:35 +0200 Subject: [PATCH 27/49] start using query builder instead of options, because it cannot help us here --- .../src/graphql/resolver/util/findContributions.ts | 13 +++++++++++++ backend/tsconfig.json | 1 + 2 files changed, 14 insertions(+) diff --git a/backend/src/graphql/resolver/util/findContributions.ts b/backend/src/graphql/resolver/util/findContributions.ts index 6ad896b9f..f8fd581e4 100644 --- a/backend/src/graphql/resolver/util/findContributions.ts +++ b/backend/src/graphql/resolver/util/findContributions.ts @@ -3,6 +3,9 @@ import { Contribution as DbContribution } from '@entity/Contribution' import { Paginated } from '@arg/Paginated' import { SearchContributionsFilterArgs } from '@arg/SearchContributionsFilterArgs' +import { Connection } from '@typeorm/connection' + +import { LogError } from '@/server/LogError' interface Relations { [key: string]: boolean | Relations @@ -14,11 +17,20 @@ export const findContributions = async ( withDeleted = false, relations: Relations | undefined = undefined, ): Promise<[DbContribution[], number]> => { + const connection = await Connection.getInstance() + if (!connection) { + throw new LogError('Cannot connect to db') + } const requiredWhere = { ...(filter.statusFilter?.length && { contributionStatus: In(filter.statusFilter) }), ...(filter.userId && { userId: filter.userId }), ...(filter.noHashtag && { memo: Not(Like(`%#%`)) }), } + const queryBuilder = connection.getRepository(DbContribution).createQueryBuilder('Contribution') + queryBuilder.where(requiredWhere) + return queryBuilder.getManyAndCount() + /* + let where = filter.query && relations?.user @@ -65,4 +77,5 @@ export const findContributions = async ( skip: (paginate.currentPage - 1) * paginate.pageSize, take: paginate.pageSize, }) + */ } diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 146cd41e3..6d27ca0fa 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -53,6 +53,7 @@ "@model/*": ["src/graphql/model/*"], "@union/*": ["src/graphql/union/*"], "@repository/*": ["src/typeorm/repository/*"], + "@typeorm/*": ["src/typeorm/*"], "@test/*": ["test/*"], /* external */ "@dbTools/*": ["../database/src/*", "../../database/build/src/*"], From 69f400a9300740cd1531c79fa586a4118fe005bd Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Fri, 18 Aug 2023 21:19:19 +0200 Subject: [PATCH 28/49] rewrite findContributions using QueryBuilder --- backend/jest.config.js | 1 + .../resolver/util/findContributions.ts | 96 ++++++++----------- backend/src/seeds/creation/index.ts | 8 ++ 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index d282f8361..8b6f53f9f 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -20,6 +20,7 @@ module.exports = { '@model/(.*)': '/src/graphql/model/$1', '@union/(.*)': '/src/graphql/union/$1', '@repository/(.*)': '/src/typeorm/repository/$1', + '@typeorm/(.*)': '/src/typeorm/$1', '@test/(.*)': '/test/$1', '@entity/(.*)': // eslint-disable-next-line n/no-process-env diff --git a/backend/src/graphql/resolver/util/findContributions.ts b/backend/src/graphql/resolver/util/findContributions.ts index f8fd581e4..48d66d883 100644 --- a/backend/src/graphql/resolver/util/findContributions.ts +++ b/backend/src/graphql/resolver/util/findContributions.ts @@ -1,4 +1,5 @@ -import { In, Like, Not } from '@dbTools/typeorm' +/* eslint-disable security/detect-object-injection */ +import { Brackets, In, Like, Not, SelectQueryBuilder } from '@dbTools/typeorm' import { Contribution as DbContribution } from '@entity/Contribution' import { Paginated } from '@arg/Paginated' @@ -11,6 +12,21 @@ interface Relations { [key: string]: boolean | Relations } +function joinRelationsRecursive( + relations: Relations, + queryBuilder: SelectQueryBuilder, + currentPath: string, +): void { + for (const key in relations) { + // console.log('leftJoin: %s, %s', `${currentPath}.${key}`, key) + queryBuilder.leftJoinAndSelect(`${currentPath}.${key}`, key) + if (typeof relations[key] === 'object') { + // If it's a nested relation + joinRelationsRecursive(relations[key] as Relations, queryBuilder, key) + } + } +} + export const findContributions = async ( paginate: Paginated, filter: SearchContributionsFilterArgs, @@ -21,61 +37,33 @@ export const findContributions = async ( if (!connection) { throw new LogError('Cannot connect to db') } - const requiredWhere = { + const queryBuilder = connection.getRepository(DbContribution).createQueryBuilder('Contribution') + if (relations) joinRelationsRecursive(relations, queryBuilder, 'Contribution') + if (withDeleted) queryBuilder.withDeleted() + queryBuilder.where({ ...(filter.statusFilter?.length && { contributionStatus: In(filter.statusFilter) }), ...(filter.userId && { userId: filter.userId }), ...(filter.noHashtag && { memo: Not(Like(`%#%`)) }), - } - const queryBuilder = connection.getRepository(DbContribution).createQueryBuilder('Contribution') - queryBuilder.where(requiredWhere) - return queryBuilder.getManyAndCount() - /* - - - let where = - filter.query && relations?.user - ? [ - { - ...requiredWhere, // And - user: { - firstName: Like(`%${filter.query}%`), - }, - }, // Or - { - ...requiredWhere, - user: { - lastName: Like(`%${filter.query}%`), - }, - }, // Or - { - ...requiredWhere, // And - user: { - emailContact: { - email: Like(`%${filter.query}%`), - }, - }, - }, // Or - { - ...requiredWhere, // And - memo: Like(`%${filter.query}%`), - }, - ] - : requiredWhere - - if (!relations?.user && filter.query) { - where = [{ ...requiredWhere, memo: Like(`%${filter.query}%`) }] - } - - return DbContribution.findAndCount({ - relations, - where, - withDeleted, - order: { - createdAt: paginate.order, - id: paginate.order, - }, - skip: (paginate.currentPage - 1) * paginate.pageSize, - take: paginate.pageSize, }) - */ + queryBuilder.printSql() + if (filter.query) { + const queryString = '%' + filter.query + '%' + queryBuilder.andWhere( + new Brackets((qb) => { + qb.where({ memo: Like(queryString) }) + if (relations?.user) { + qb.orWhere('user.first_name LIKE :firstName', { firstName: queryString }) + .orWhere('user.last_name LIKE :lastName', { lastName: queryString }) + .orWhere('emailContact.email LIKE :emailContact', { emailContact: queryString }) + .orWhere({ memo: Like(queryString) }) + } + }), + ) + } + return queryBuilder + .orderBy('Contribution.createdAt', paginate.order) + .addOrderBy('Contribution.id', paginate.order) + .skip((paginate.currentPage - 1) * paginate.pageSize) + .take(paginate.pageSize) + .getManyAndCount() } diff --git a/backend/src/seeds/creation/index.ts b/backend/src/seeds/creation/index.ts index 3f2a545a4..c22a99b0c 100644 --- a/backend/src/seeds/creation/index.ts +++ b/backend/src/seeds/creation/index.ts @@ -136,6 +136,14 @@ export const creations: CreationInterface[] = [ confirmed: true, moveCreationDate: 12, }, + { + email: 'bibi@bloxberg.de', + amount: 1000, + memo: '#Hexen', + creationDate: nMonthsBefore(new Date()), + confirmed: true, + moveCreationDate: 12, + }, ...bobsTransactions, { email: 'raeuber@hotzenplotz.de', From 3d23fbb10c4f8acbd777654f9a45b04f21054a68 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 18 Aug 2023 22:10:14 +0200 Subject: [PATCH 29/49] now with writing verified communities in communities table --- .../src/federation/client/1_0/model/PublicCommunityInfo.ts | 2 +- .../federation/client/1_0/query/getPublicCommunityInfo.ts | 2 +- backend/src/federation/validateCommunities.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts b/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts index 88f8e8eeb..cad8176be 100644 --- a/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts +++ b/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts @@ -1,6 +1,6 @@ export interface PublicCommunityInfo { name: string description: string - createdAt: Date + creationDate: Date publicKey: string } diff --git a/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts b/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts index 4dead00bd..f075b2aae 100644 --- a/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts +++ b/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts @@ -5,7 +5,7 @@ export const getPublicCommunityInfo = gql` getPublicCommunityInfo { name description - createdAt + creationDate publicKey } } diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 75384726e..b76e77bd7 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -55,6 +55,8 @@ export async function validateCommunities(): Promise { if (pubComInfo) { await writeForeignCommunity(dbCom, pubComInfo) logger.info(`Federation: write publicInfo of community: name=${pubComInfo.name}`) + } else { + logger.warn('Federation: missing result of getPublicCommunityInfo') } } else { logger.warn( @@ -74,7 +76,7 @@ async function writeForeignCommunity( dbCom: DbFederatedCommunity, pubInfo: PublicCommunityInfo, ): Promise { - if (!dbCom || !pubInfo || !(dbCom.publicKey.toString('hex') === pubInfo.publicKey)) { + if (!dbCom || !pubInfo || !(dbCom.publicKey.toString() === pubInfo.publicKey)) { logger.error( `Error in writeForeignCommunity: missmatching parameters or publicKey. pubInfo:${JSON.stringify( pubInfo, @@ -85,7 +87,7 @@ async function writeForeignCommunity( if (!com) { com = DbCommunity.create() } - com.creationDate = pubInfo.createdAt + com.creationDate = pubInfo.creationDate com.description = pubInfo.description com.foreign = true com.name = pubInfo.name From e310d46ac908f52dd81ba75b49dcdb25b377a693 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Sat, 19 Aug 2023 09:27:12 +0200 Subject: [PATCH 30/49] add tests for hashtag --- .../resolver/ContributionResolver.test.ts | 140 ++++++++++++++++-- backend/src/seeds/graphql/queries.ts | 2 + 2 files changed, 129 insertions(+), 13 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index cbb92eff8..a678c7531 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -2718,22 +2718,40 @@ describe('ContributionResolver', () => { mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) + await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: '#firefighters', + creationDate: new Date().toString(), + }, + }) }) afterAll(() => { resetToken() }) - it('returns 17 creations in total', async () => { + it('returns 18 creations in total', async () => { const { data: { adminListContributions: contributionListObject }, } = await query({ query: adminListContributions, }) - expect(contributionListObject.contributionList).toHaveLength(17) + // console.log('17 contributions: %s', JSON.stringify(contributionListObject, null, 2)) + expect(contributionListObject.contributionList).toHaveLength(18) expect(contributionListObject).toMatchObject({ - contributionCount: 17, + contributionCount: 18, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: expect.decimalEqual(100), + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: '#firefighters', + messagesCount: 0, + status: 'PENDING', + }), expect.objectContaining({ amount: expect.decimalEqual(50), firstName: 'Bibi', @@ -2905,8 +2923,17 @@ describe('ContributionResolver', () => { }) expect(contributionListObject.contributionList).toHaveLength(2) expect(contributionListObject).toMatchObject({ - contributionCount: 4, + contributionCount: 5, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: '#firefighters', + messagesCount: 0, + status: 'PENDING', + }), expect.objectContaining({ amount: '400', firstName: 'Peter', @@ -2916,15 +2943,6 @@ describe('ContributionResolver', () => { messagesCount: 0, status: 'PENDING', }), - expect.objectContaining({ - amount: '100', - firstName: 'Peter', - id: expect.any(Number), - lastName: 'Lustig', - memo: 'Test env contribution', - messagesCount: 0, - status: 'PENDING', - }), expect.not.objectContaining({ status: 'DENIED', }), @@ -2951,6 +2969,60 @@ describe('ContributionResolver', () => { query: 'Peter', }, }) + expect(contributionListObject.contributionList).toHaveLength(4) + expect(contributionListObject).toMatchObject({ + contributionCount: 4, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: expect.decimalEqual(100), + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: '#firefighters', + messagesCount: 0, + status: 'PENDING', + }), + expect.objectContaining({ + amount: expect.decimalEqual(400), + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: 'Herzlich Willkommen bei Gradido!', + messagesCount: 0, + status: 'PENDING', + }), + expect.objectContaining({ + amount: expect.decimalEqual(100), + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: 'Test env contribution', + messagesCount: 0, + status: 'PENDING', + }), + expect.objectContaining({ + amount: expect.decimalEqual(200), + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: 'Das war leider zu Viel!', + messagesCount: 0, + status: 'DELETED', + }), + ]), + }) + }) + + it('returns only contributions of the queried user without hashtags', async () => { + const { + data: { adminListContributions: contributionListObject }, + } = await query({ + query: adminListContributions, + variables: { + query: 'Peter', + noHashtag: true, + }, + }) expect(contributionListObject.contributionList).toHaveLength(3) expect(contributionListObject).toMatchObject({ contributionCount: 3, @@ -2986,6 +3058,48 @@ describe('ContributionResolver', () => { }) }) + it('returns only contributions with #firefighter', async () => { + const { + data: { adminListContributions: contributionListObject }, + } = await query({ + query: adminListContributions, + variables: { + query: '#firefighter', + }, + }) + expect(contributionListObject.contributionList).toHaveLength(1) + expect(contributionListObject).toMatchObject({ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: expect.decimalEqual(100), + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: '#firefighters', + messagesCount: 0, + status: 'PENDING', + }), + ]), + }) + }) + + it('returns no contributions with #firefighter and no hashtag', async () => { + const { + data: { adminListContributions: contributionListObject }, + } = await query({ + query: adminListContributions, + variables: { + query: '#firefighter', + noHashtag: true, + }, + }) + expect(contributionListObject.contributionList).toHaveLength(0) + expect(contributionListObject).toMatchObject({ + contributionCount: 0, + }) + }) + // test for case sensitivity and email it('returns only contributions of the queried user email', async () => { const { diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index f016102a2..3a2823a65 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -236,6 +236,7 @@ export const adminListContributions = gql` $statusFilter: [ContributionStatus!] $userId: Int $query: String + $noHashtag: Boolean ) { adminListContributions( currentPage: $currentPage @@ -244,6 +245,7 @@ export const adminListContributions = gql` statusFilter: $statusFilter userId: $userId query: $query + noHashtag: $noHashtag ) { contributionCount contributionList { From 3333184542d28686260a779a1036ce9b80fb44df Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Sat, 19 Aug 2023 09:48:01 +0200 Subject: [PATCH 31/49] update locales and swap button with checkbox --- admin/src/locales/de.json | 9 +++------ admin/src/locales/en.json | 9 +++------ admin/src/pages/CreationConfirm.vue | 17 ++++------------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 3a4f23b18..33ef36053 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -89,7 +89,6 @@ "submit": "Senden" }, "GDD": "GDD", - "hashtag_symbol": "#", "help": { "help": "Hilfe", "transactionlist": { @@ -125,10 +124,8 @@ "user_search": "Nutzersuche" }, "not_open_creations": "Keine offenen Schöpfungen", - "no_filter": "Keine Filterung", - "no_filter_tooltip": "Es wird nicht nach Hashtags gefiltert", - "no_hashtag": "Ohne Hashtag", - "no_hashtag_tooltip": "Zeigt nur Schöpfungen ohne Hashtag im Kommentar an", + "no_hashtag": "#Hashtags verbergen", + "no_hashtag_tooltip": "Zeigt nur Beiträge ohne Hashtag im Text", "open": "offen", "open_creations": "Offene Schöpfungen", "overlay": { @@ -221,7 +218,7 @@ "tabTitle": "Nutzer-Rolle" }, "user_deleted": "Nutzer ist gelöscht.", - "user_memo_search": "Nutzer-Kommentar-Suche", + "user_memo_search": "Benutzer- und Text-Suche", "user_recovered": "Nutzer ist wiederhergestellt.", "user_search": "Nutzer-Suche" } diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index 5a3c78a0f..6c8b36f15 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -89,7 +89,6 @@ "submit": "Send" }, "GDD": "GDD", - "hashtag_symbol": "#", "help": { "help": "Help", "transactionlist": { @@ -125,10 +124,8 @@ "user_search": "User search" }, "not_open_creations": "No open creations", - "no_filter": "No Filter", - "no_filter_tooltip": "It is not filtered by hashtags", - "no_hashtag": "No Hashtag", - "no_hashtag_tooltip": "Displays only contributions without hashtag in comment", + "no_hashtag": "Hide #hashtags", + "no_hashtag_tooltip": "Shows only contributions without hashtag in text", "open": "open", "open_creations": "Open creations", "overlay": { @@ -221,7 +218,7 @@ "tabTitle": "User Role" }, "user_deleted": "User is deleted.", - "user_memo_search": "User and Memo search", + "user_memo_search": "User and text search", "user_recovered": "User is recovered.", "user_search": "User search" } diff --git a/admin/src/pages/CreationConfirm.vue b/admin/src/pages/CreationConfirm.vue index 7d52c4ce2..bd4c58983 100644 --- a/admin/src/pages/CreationConfirm.vue +++ b/admin/src/pages/CreationConfirm.vue @@ -2,12 +2,10 @@