mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2897-incorrect-errormessage-for-wrong-contribution-link
This commit is contained in:
commit
245d9ddb8a
41
CHANGELOG.md
41
CHANGELOG.md
@ -4,8 +4,49 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [1.21.0](https://github.com/gradido/gradido/compare/1.20.0...1.21.0)
|
||||||
|
|
||||||
|
- feat(frontend): preserve email after login [`#2994`](https://github.com/gradido/gradido/pull/2994)
|
||||||
|
- feat(frontend): send coins via identifier [`#2989`](https://github.com/gradido/gradido/pull/2989)
|
||||||
|
- feat(backend): export user events to klicktipp [`#2916`](https://github.com/gradido/gradido/pull/2916)
|
||||||
|
- fix(backend): add extension pug json and css to nodemon. [`#2996`](https://github.com/gradido/gradido/pull/2996)
|
||||||
|
- feat(backend): send coins via alias [`#2988`](https://github.com/gradido/gradido/pull/2988)
|
||||||
|
- refactor(backend): replace jasonwebtoken with jose [`#2975`](https://github.com/gradido/gradido/pull/2975)
|
||||||
|
- feat(frontend): username in wallet [`#2984`](https://github.com/gradido/gradido/pull/2984)
|
||||||
|
- feat(frontend): add community to send form [`#2986`](https://github.com/gradido/gradido/pull/2986)
|
||||||
|
- fix(frontend): date fns locales [`#2983`](https://github.com/gradido/gradido/pull/2983)
|
||||||
|
- refactor(federation): federation reduce spam [`#2967`](https://github.com/gradido/gradido/pull/2967)
|
||||||
|
- refactor(federation): refactor federation clients [`#2965`](https://github.com/gradido/gradido/pull/2965)
|
||||||
|
- feat(backend): migrate transactions table for x community sendcoins [`#2917`](https://github.com/gradido/gradido/pull/2917)
|
||||||
|
- feat(backend): alias in update user info [`#2727`](https://github.com/gradido/gradido/pull/2727)
|
||||||
|
- refactor(backend): eslint comments [`#2981`](https://github.com/gradido/gradido/pull/2981)
|
||||||
|
- refactor(backend): eslint security [`#2980`](https://github.com/gradido/gradido/pull/2980)
|
||||||
|
- refactor(backend): rename klicktippSignIn to subscribe. [`#2973`](https://github.com/gradido/gradido/pull/2973)
|
||||||
|
- refactor(backend): eslint typescript strict [`#2979`](https://github.com/gradido/gradido/pull/2979)
|
||||||
|
- fix(frontend): between store problems [`#2972`](https://github.com/gradido/gradido/pull/2972)
|
||||||
|
- refactor(other): delete build folders [`#2977`](https://github.com/gradido/gradido/pull/2977)
|
||||||
|
- refactor(backend): no email in user [`#2953`](https://github.com/gradido/gradido/pull/2953)
|
||||||
|
- refactor(frontend): remove email in wallet [`#2952`](https://github.com/gradido/gradido/pull/2952)
|
||||||
|
- fix(frontend): update jest-canvas-mock version to resolve window mock problem in tests [`#2974`](https://github.com/gradido/gradido/pull/2974)
|
||||||
|
- feat(federation): federation autoreload on codechange [`#2969`](https://github.com/gradido/gradido/pull/2969)
|
||||||
|
- feat(backend): add fields to subscriber [`#2887`](https://github.com/gradido/gradido/pull/2887)
|
||||||
|
- feat(backend): x-com-2: distingue communities and communities_federation in database [`#2890`](https://github.com/gradido/gradido/pull/2890)
|
||||||
|
- feat(backend): add event for subscribe and unsubscribe [`#2886`](https://github.com/gradido/gradido/pull/2886)
|
||||||
|
- refactor(backend): eslint disable more typesafety [`#2922`](https://github.com/gradido/gradido/pull/2922)
|
||||||
|
- refactor(backend): eslint disable tests typesafer [`#2921`](https://github.com/gradido/gradido/pull/2921)
|
||||||
|
- refactor(backend): eslint disable @typescript eslint/unbound method [`#2920`](https://github.com/gradido/gradido/pull/2920)
|
||||||
|
- docs(other): removed obsolete yarn cron docu [`#2909`](https://github.com/gradido/gradido/pull/2909)
|
||||||
|
- refactor(other): finalize workflow separation and resolve mariadb and database dependencies in workflow files [`#2962`](https://github.com/gradido/gradido/pull/2962)
|
||||||
|
- refactor(workflow): align workflow naming and remove docker-compose filter from build tests [`#2894`](https://github.com/gradido/gradido/pull/2894)
|
||||||
|
- refactor(backend): eslint plugin promise + fixes [`#2830`](https://github.com/gradido/gradido/pull/2830)
|
||||||
|
- fix(backend): log stack trace included [`#2915`](https://github.com/gradido/gradido/pull/2915)
|
||||||
|
- refactor(backend): prettier refine config [`#2832`](https://github.com/gradido/gradido/pull/2832)
|
||||||
|
|
||||||
#### [1.20.0](https://github.com/gradido/gradido/compare/1.19.1...1.20.0)
|
#### [1.20.0](https://github.com/gradido/gradido/compare/1.19.1...1.20.0)
|
||||||
|
|
||||||
|
> 12 April 2023
|
||||||
|
|
||||||
|
- chore(release): v1.20.0 [`#2939`](https://github.com/gradido/gradido/pull/2939)
|
||||||
- fix(backend): no await for emails [`#2918`](https://github.com/gradido/gradido/pull/2918)
|
- fix(backend): no await for emails [`#2918`](https://github.com/gradido/gradido/pull/2918)
|
||||||
- fix(frontend): no receiver on send by link [`#2933`](https://github.com/gradido/gradido/pull/2933)
|
- fix(frontend): no receiver on send by link [`#2933`](https://github.com/gradido/gradido/pull/2933)
|
||||||
- fix(admin): pagination set currentPage by switch tabs [`#2902`](https://github.com/gradido/gradido/pull/2902)
|
- fix(admin): pagination set currentPage by switch tabs [`#2902`](https://github.com/gradido/gradido/pull/2902)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Administraion Interface for Gradido",
|
"description": "Administraion Interface for Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Moriz Wahl",
|
"author": "Moriz Wahl",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -27,7 +27,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': ['error'],
|
'no-console': 'error',
|
||||||
|
camelcase: ['error', { allow: ['FederationClient_*'] }],
|
||||||
'no-debugger': 'error',
|
'no-debugger': 'error',
|
||||||
'prettier/prettier': [
|
'prettier/prettier': [
|
||||||
'error',
|
'error',
|
||||||
@ -184,6 +185,7 @@ module.exports = {
|
|||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
project: ['./tsconfig.json', '**/tsconfig.json'],
|
project: ['./tsconfig.json', '**/tsconfig.json'],
|
||||||
// this is to properly reference the referenced project database without requirement of compiling it
|
// this is to properly reference the referenced project database without requirement of compiling it
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/backend",
|
"repository": "https://github.com/gradido/gradido/backend",
|
||||||
@ -11,11 +11,11 @@
|
|||||||
"build": "tsc --build && mkdir -p build/src/emails/templates/ && cp -r src/emails/templates/* build/src/emails/templates/ && mkdir -p build/src/locales/ && cp -r src/locales/*.json build/src/locales/",
|
"build": "tsc --build && mkdir -p build/src/emails/templates/ && cp -r src/emails/templates/* build/src/emails/templates/ && mkdir -p build/src/locales/ && cp -r src/locales/*.json build/src/locales/",
|
||||||
"clean": "tsc --build --clean",
|
"clean": "tsc --build --clean",
|
||||||
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
|
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
|
||||||
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts",
|
"dev": "cross-env TZ=UTC nodemon -w src --ext ts,pug,json,css --exec ts-node -r tsconfig-paths/register src/index.ts",
|
||||||
"lint": "eslint --max-warnings=0 .",
|
"lint": "eslint --max-warnings=0 .",
|
||||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles",
|
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles",
|
||||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
||||||
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts",
|
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/executeKlicktipp.ts",
|
||||||
"locales": "scripts/sort.sh"
|
"locales": "scripts/sort.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { CONFIG } from '@/config'
|
import { CONFIG } from '@/config'
|
||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-parent-imports
|
// eslint-disable-next-line import/no-relative-parent-imports
|
||||||
import KlicktippConnector from 'klicktipp-api'
|
import KlicktippConnector from 'klicktipp-api'
|
||||||
@ -41,9 +41,12 @@ export const getKlickTippUser = async (email: string): Promise<any> => {
|
|||||||
if (!CONFIG.KLICKTIPP) return true
|
if (!CONFIG.KLICKTIPP) return true
|
||||||
const isLogin = await loginKlicktippUser()
|
const isLogin = await loginKlicktippUser()
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
const subscriberId = await klicktippConnector.subscriberSearch(email)
|
try {
|
||||||
const result = await klicktippConnector.subscriberGet(subscriberId)
|
return klicktippConnector.subscriberGet(await klicktippConnector.subscriberSearch(email))
|
||||||
return result
|
} catch (e) {
|
||||||
|
logger.error('Could not find subscriber', email)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -62,8 +65,18 @@ export const addFieldsToSubscriber = async (
|
|||||||
if (!CONFIG.KLICKTIPP) return true
|
if (!CONFIG.KLICKTIPP) return true
|
||||||
const isLogin = await loginKlicktippUser()
|
const isLogin = await loginKlicktippUser()
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
const subscriberId = await klicktippConnector.subscriberSearch(email)
|
try {
|
||||||
return klicktippConnector.subscriberUpdate(subscriberId, fields, newemail, newsmsnumber)
|
logger.info('Updating of subscriber', email)
|
||||||
|
return klicktippConnector.subscriberUpdate(
|
||||||
|
await klicktippConnector.subscriberSearch(email),
|
||||||
|
fields,
|
||||||
|
newemail,
|
||||||
|
newsmsnumber,
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Could not update subscriber', email, fields, e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { GraphQLClient } from 'graphql-request'
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
|
||||||
import { getPublicKey } from '@/federation/query/getPublicKey'
|
import { getPublicKey } from '@/federation/client/1_0/query/getPublicKey'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
export class Client_1_0 {
|
export class FederationClient {
|
||||||
dbCom: DbFederatedCommunity
|
dbCom: DbFederatedCommunity
|
||||||
endpoint: string
|
endpoint: string
|
||||||
client: GraphQLClient
|
client: GraphQLClient
|
||||||
5
backend/src/federation/client/1_1/FederationClient.ts
Normal file
5
backend/src/federation/client/1_1/FederationClient.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
export class FederationClient extends V1_0_FederationClient {}
|
||||||
@ -1,5 +0,0 @@
|
|||||||
// eslint-disable-next-line camelcase
|
|
||||||
import { Client_1_0 } from './Client_1_0'
|
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
export class Client_1_1 extends Client_1_0 {}
|
|
||||||
@ -1,24 +1,23 @@
|
|||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_1_FederationClient } from '@/federation/client/1_1/FederationClient'
|
||||||
import { ApiVersionType } from '@/federation/enum/apiVersionType'
|
import { ApiVersionType } from '@/federation/enum/apiVersionType'
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
import { Client_1_0 } from './Client_1_0'
|
type FederationClient = V1_0_FederationClient | V1_1_FederationClient
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
import { Client_1_1 } from './Client_1_1'
|
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
interface FederationClientInstance {
|
||||||
type FederationClient = Client_1_0 | Client_1_1
|
|
||||||
|
|
||||||
interface ClientInstance {
|
|
||||||
id: number
|
id: number
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
client: FederationClient
|
client: FederationClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||||
export class Client {
|
export class FederationClientFactory {
|
||||||
private static instanceArray: ClientInstance[] = []
|
private static instanceArray: FederationClientInstance[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Singleton's constructor should always be private to prevent direct
|
* The Singleton's constructor should always be private to prevent direct
|
||||||
@ -30,9 +29,9 @@ export class Client {
|
|||||||
private static createFederationClient = (dbCom: DbFederatedCommunity) => {
|
private static createFederationClient = (dbCom: DbFederatedCommunity) => {
|
||||||
switch (dbCom.apiVersion) {
|
switch (dbCom.apiVersion) {
|
||||||
case ApiVersionType.V1_0:
|
case ApiVersionType.V1_0:
|
||||||
return new Client_1_0(dbCom)
|
return new V1_0_FederationClient(dbCom)
|
||||||
case ApiVersionType.V1_1:
|
case ApiVersionType.V1_1:
|
||||||
return new Client_1_1(dbCom)
|
return new V1_1_FederationClient(dbCom)
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -45,13 +44,18 @@ export class Client {
|
|||||||
* just one instance of each subclass around.
|
* just one instance of each subclass around.
|
||||||
*/
|
*/
|
||||||
public static getInstance(dbCom: DbFederatedCommunity): FederationClient | null {
|
public static getInstance(dbCom: DbFederatedCommunity): FederationClient | null {
|
||||||
const instance = Client.instanceArray.find((instance) => instance.id === dbCom.id)
|
const instance = FederationClientFactory.instanceArray.find(
|
||||||
|
(instance) => instance.id === dbCom.id,
|
||||||
|
)
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance.client
|
return instance.client
|
||||||
}
|
}
|
||||||
const client = Client.createFederationClient(dbCom)
|
const client = FederationClientFactory.createFederationClient(dbCom)
|
||||||
if (client) {
|
if (client) {
|
||||||
Client.instanceArray.push({ id: dbCom.id, client } as ClientInstance)
|
FederationClientFactory.instanceArray.push({
|
||||||
|
id: dbCom.id,
|
||||||
|
client,
|
||||||
|
} as FederationClientInstance)
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
@ -8,6 +8,8 @@
|
|||||||
import { Connection } from '@dbTools/typeorm'
|
import { Connection } from '@dbTools/typeorm'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
import { Response } from 'graphql-request/dist/types'
|
||||||
|
|
||||||
import { testEnvironment, cleanDB } from '@test/helpers'
|
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||||
import { logger } from '@test/testSetup'
|
import { logger } from '@test/testSetup'
|
||||||
@ -57,10 +59,23 @@ describe('validate Communities', () => {
|
|||||||
expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`)
|
expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with one Community of api 1_0', () => {
|
describe('with one Community of api 1_0 and not matching pubKey', () => {
|
||||||
beforeEach(async () => {
|
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: 'somePubKey',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Response<unknown>
|
||||||
|
})
|
||||||
const variables1 = {
|
const variables1 = {
|
||||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
apiVersion: '1_0',
|
apiVersion: '1_0',
|
||||||
endPoint: 'http//localhost:5001/api/',
|
endPoint: 'http//localhost:5001/api/',
|
||||||
lastAnnouncedAt: new Date(),
|
lastAnnouncedAt: new Date(),
|
||||||
@ -70,6 +85,7 @@ describe('validate Communities', () => {
|
|||||||
.into(DbFederatedCommunity)
|
.into(DbFederatedCommunity)
|
||||||
.values(variables1)
|
.values(variables1)
|
||||||
.orUpdate({
|
.orUpdate({
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
overwrite: ['end_point', 'last_announced_at'],
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
})
|
})
|
||||||
@ -88,11 +104,85 @@ describe('validate Communities', () => {
|
|||||||
'http//localhost:5001/api/1_0/',
|
'http//localhost:5001/api/1_0/',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
it('logs not matching publicKeys', () => {
|
||||||
|
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<unknown>
|
||||||
|
})
|
||||||
|
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', () => {
|
describe('with two Communities of api 1_0 and 1_1', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
// 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<unknown>
|
||||||
|
})
|
||||||
const variables2 = {
|
const variables2 = {
|
||||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
apiVersion: '1_1',
|
apiVersion: '1_1',
|
||||||
endPoint: 'http//localhost:5001/api/',
|
endPoint: 'http//localhost:5001/api/',
|
||||||
lastAnnouncedAt: new Date(),
|
lastAnnouncedAt: new Date(),
|
||||||
@ -102,11 +192,13 @@ describe('validate Communities', () => {
|
|||||||
.into(DbFederatedCommunity)
|
.into(DbFederatedCommunity)
|
||||||
.values(variables2)
|
.values(variables2)
|
||||||
.orUpdate({
|
.orUpdate({
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
overwrite: ['end_point', 'last_announced_at'],
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
})
|
})
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
|
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await validateCommunities()
|
await validateCommunities()
|
||||||
})
|
})
|
||||||
@ -130,7 +222,9 @@ describe('validate Communities', () => {
|
|||||||
let dbCom: DbFederatedCommunity
|
let dbCom: DbFederatedCommunity
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const variables3 = {
|
const variables3 = {
|
||||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
apiVersion: '2_0',
|
apiVersion: '2_0',
|
||||||
endPoint: 'http//localhost:5001/api/',
|
endPoint: 'http//localhost:5001/api/',
|
||||||
lastAnnouncedAt: new Date(),
|
lastAnnouncedAt: new Date(),
|
||||||
@ -140,6 +234,7 @@ describe('validate Communities', () => {
|
|||||||
.into(DbFederatedCommunity)
|
.into(DbFederatedCommunity)
|
||||||
.values(variables3)
|
.values(variables3)
|
||||||
.orUpdate({
|
.orUpdate({
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
overwrite: ['end_point', 'last_announced_at'],
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
})
|
})
|
||||||
@ -147,6 +242,7 @@ describe('validate Communities', () => {
|
|||||||
dbCom = await DbFederatedCommunity.findOneOrFail({
|
dbCom = await DbFederatedCommunity.findOneOrFail({
|
||||||
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
|
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
|
||||||
})
|
})
|
||||||
|
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await validateCommunities()
|
await validateCommunities()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
import { IsNull } from '@dbTools/typeorm'
|
import { IsNull } from '@dbTools/typeorm'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
|
||||||
|
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
import { Client } from './client/Client'
|
|
||||||
import { ApiVersionType } from './enum/apiVersionType'
|
import { ApiVersionType } from './enum/apiVersionType'
|
||||||
|
|
||||||
export function startValidateCommunities(timerInterval: number): void {
|
export function startValidateCommunities(timerInterval: number): void {
|
||||||
@ -37,11 +39,13 @@ export async function validateCommunities(): Promise<void> {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const client = Client.getInstance(dbCom)
|
const client = FederationClientFactory.getInstance(dbCom)
|
||||||
const pubKey = await client?.getPublicKey()
|
// eslint-disable-next-line camelcase
|
||||||
|
if (client instanceof V1_0_FederationClient) {
|
||||||
|
const pubKey = await client.getPublicKey()
|
||||||
if (pubKey && pubKey === dbCom.publicKey.toString()) {
|
if (pubKey && pubKey === dbCom.publicKey.toString()) {
|
||||||
await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
|
await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
|
||||||
logger.info('Federation: verified community', dbCom)
|
logger.info('Federation: verified community with', dbCom.endPoint)
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Federation: received not matching publicKey:',
|
'Federation: received not matching publicKey:',
|
||||||
@ -49,6 +53,7 @@ export async function validateCommunities(): Promise<void> {
|
|||||||
dbCom.publicKey.toString(),
|
dbCom.publicKey.toString(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Error:`, err)
|
logger.error(`Error:`, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -322,8 +322,6 @@ export class TransactionResolver {
|
|||||||
throw new LogError('Amount to send must be positive', amount)
|
throw new LogError('Amount to send must be positive', amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is subject to replay attacks
|
|
||||||
// --- WHY?
|
|
||||||
const senderUser = getUser(context)
|
const senderUser = getUser(context)
|
||||||
|
|
||||||
// validate recipient user
|
// validate recipient user
|
||||||
|
|||||||
17
backend/src/graphql/resolver/util/eventList.ts
Normal file
17
backend/src/graphql/resolver/util/eventList.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Event as DbEvent } from '@entity/Event'
|
||||||
|
import { User } from '@entity/User'
|
||||||
|
import { UserContact } from '@entity/UserContact'
|
||||||
|
|
||||||
|
export const lastDateTimeEvents = async (
|
||||||
|
eventType: string,
|
||||||
|
): Promise<{ email: string; value: Date }[]> => {
|
||||||
|
return DbEvent.createQueryBuilder('event')
|
||||||
|
.select('MAX(event.created_at)', 'value')
|
||||||
|
.leftJoin(User, 'user', 'affected_user_id = user.id')
|
||||||
|
.leftJoin(UserContact, 'usercontact', 'user.id = usercontact.user_id')
|
||||||
|
.addSelect('usercontact.email', 'email')
|
||||||
|
.where('event.type = :eventType', { eventType })
|
||||||
|
.andWhere('usercontact.email IS NOT NULL')
|
||||||
|
.groupBy('event.affected_user_id')
|
||||||
|
.getRawMany()
|
||||||
|
}
|
||||||
@ -1,14 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
import { Connection } from '@dbTools/typeorm'
|
import { Connection as DbConnection } from '@dbTools/typeorm'
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import express, { Express, json, urlencoded } from 'express'
|
import express, { Express, json, urlencoded } from 'express'
|
||||||
import { Logger } from 'log4js'
|
import { Logger } from 'log4js'
|
||||||
|
|
||||||
import { CONFIG } from '@/config'
|
import { CONFIG } from '@/config'
|
||||||
import { schema } from '@/graphql/schema'
|
import { schema } from '@/graphql/schema'
|
||||||
import { connection } from '@/typeorm/connection'
|
import { Connection } from '@/typeorm/connection'
|
||||||
import { checkDBVersion } from '@/typeorm/DBVersion'
|
import { checkDBVersion } from '@/typeorm/DBVersion'
|
||||||
import { elopageWebhook } from '@/webhook/elopage'
|
import { elopageWebhook } from '@/webhook/elopage'
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ import { plugins } from './plugins'
|
|||||||
interface ServerDef {
|
interface ServerDef {
|
||||||
apollo: ApolloServer
|
apollo: ApolloServer
|
||||||
app: Express
|
app: Express
|
||||||
con: Connection
|
con: DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createServer = async (
|
export const createServer = async (
|
||||||
@ -37,7 +37,7 @@ export const createServer = async (
|
|||||||
logger.debug('createServer...')
|
logger.debug('createServer...')
|
||||||
|
|
||||||
// open mysql connection
|
// open mysql connection
|
||||||
const con = await connection()
|
const con = await Connection.getInstance()
|
||||||
if (!con?.isConnected) {
|
if (!con?.isConnected) {
|
||||||
logger.fatal(`Couldn't open connection to database!`)
|
logger.fatal(`Couldn't open connection to database!`)
|
||||||
throw new Error(`Fatal: Couldn't open connection to database`)
|
throw new Error(`Fatal: Couldn't open connection to database`)
|
||||||
|
|||||||
@ -1,13 +1,33 @@
|
|||||||
// TODO This is super weird - since the entities are defined in another project they have their own globals.
|
// TODO This is super weird - since the entities are defined in another project they have their own globals.
|
||||||
// We cannot use our connection here, but must use the external typeorm installation
|
// We cannot use our connection here, but must use the external typeorm installation
|
||||||
import { Connection, createConnection, FileLogger } from '@dbTools/typeorm'
|
import { Connection as DbConnection, createConnection, FileLogger } from '@dbTools/typeorm'
|
||||||
import { entities } from '@entity/index'
|
import { entities } from '@entity/index'
|
||||||
|
|
||||||
import { CONFIG } from '@/config'
|
import { CONFIG } from '@/config'
|
||||||
|
|
||||||
export const connection = async (): Promise<Connection | null> => {
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||||
|
export class Connection {
|
||||||
|
private static instance: DbConnection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Singleton's constructor should always be private to prevent direct
|
||||||
|
* construction calls with the `new` operator.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The static method that controls the access to the singleton instance.
|
||||||
|
*
|
||||||
|
* This implementation let you subclass the Singleton class while keeping
|
||||||
|
* just one instance of each subclass around.
|
||||||
|
*/
|
||||||
|
public static async getInstance(): Promise<DbConnection | null> {
|
||||||
|
if (Connection.instance) {
|
||||||
|
return Connection.instance
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return createConnection({
|
Connection.instance = await createConnection({
|
||||||
name: 'default',
|
name: 'default',
|
||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
host: CONFIG.DB_HOST,
|
host: CONFIG.DB_HOST,
|
||||||
@ -25,9 +45,11 @@ export const connection = async (): Promise<Connection | null> => {
|
|||||||
charset: 'utf8mb4_unicode_ci',
|
charset: 'utf8mb4_unicode_ci',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
return Connection.instance
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error)
|
console.log(error)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
16
backend/src/util/executeKlicktipp.ts
Normal file
16
backend/src/util/executeKlicktipp.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Connection } from '@/typeorm/connection'
|
||||||
|
|
||||||
|
import { exportEventDataToKlickTipp } from './klicktipp'
|
||||||
|
|
||||||
|
async function executeKlicktipp(): Promise<boolean> {
|
||||||
|
const connection = await Connection.getInstance()
|
||||||
|
if (connection) {
|
||||||
|
await exportEventDataToKlickTipp()
|
||||||
|
await connection.close()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeKlicktipp()
|
||||||
65
backend/src/util/klicktipp.test.ts
Normal file
65
backend/src/util/klicktipp.test.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
import { Connection } from '@dbTools/typeorm'
|
||||||
|
import { Event as DbEvent } from '@entity/Event'
|
||||||
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
|
import { testEnvironment, cleanDB, resetToken } from '@test/helpers'
|
||||||
|
|
||||||
|
import { addFieldsToSubscriber } from '@/apis/KlicktippController'
|
||||||
|
import { creations } from '@/seeds/creation'
|
||||||
|
import { creationFactory } from '@/seeds/factory/creation'
|
||||||
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
|
import { login } from '@/seeds/graphql/mutations'
|
||||||
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
|
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||||
|
|
||||||
|
import { exportEventDataToKlickTipp } from './klicktipp'
|
||||||
|
|
||||||
|
jest.mock('@/apis/KlicktippController')
|
||||||
|
|
||||||
|
let mutate: ApolloServerTestClient['mutate'], con: Connection
|
||||||
|
let testEnv: {
|
||||||
|
mutate: ApolloServerTestClient['mutate']
|
||||||
|
query: ApolloServerTestClient['query']
|
||||||
|
con: Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testEnv = await testEnvironment()
|
||||||
|
mutate = testEnv.mutate
|
||||||
|
con = testEnv.con
|
||||||
|
await DbEvent.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
await con.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('klicktipp', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await userFactory(testEnv, peterLustig)
|
||||||
|
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
await creationFactory(testEnv, bibisCreation!)
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('exportEventDataToKlickTipp', () => {
|
||||||
|
it('calls the KlicktippController', async () => {
|
||||||
|
await exportEventDataToKlickTipp()
|
||||||
|
expect(addFieldsToSubscriber).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,14 +1,11 @@
|
|||||||
|
// eslint-disable @typescript-eslint/no-explicit-any
|
||||||
import { User } from '@entity/User'
|
import { User } from '@entity/User'
|
||||||
|
|
||||||
import { getKlickTippUser } from '@/apis/KlicktippController'
|
import { getKlickTippUser, addFieldsToSubscriber } from '@/apis/KlicktippController'
|
||||||
import { LogError } from '@/server/LogError'
|
import { EventType } from '@/event/EventType'
|
||||||
import { connection } from '@/typeorm/connection'
|
import { lastDateTimeEvents } from '@/graphql/resolver/util/eventList'
|
||||||
|
|
||||||
export async function retrieveNotRegisteredEmails(): Promise<string[]> {
|
export async function retrieveNotRegisteredEmails(): Promise<string[]> {
|
||||||
const con = await connection()
|
|
||||||
if (!con) {
|
|
||||||
throw new LogError('No connection to database')
|
|
||||||
}
|
|
||||||
const users = await User.find({ relations: ['emailContact'] })
|
const users = await User.find({ relations: ['emailContact'] })
|
||||||
const notRegisteredUser = []
|
const notRegisteredUser = []
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
@ -20,10 +17,39 @@ export async function retrieveNotRegisteredEmails(): Promise<string[]> {
|
|||||||
console.log(`${user.emailContact.email}`)
|
console.log(`${user.emailContact.email}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await con.close()
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('User die nicht bei KlickTipp vorhanden sind: ', notRegisteredUser)
|
console.log('User die nicht bei KlickTipp vorhanden sind: ', notRegisteredUser)
|
||||||
return notRegisteredUser
|
return notRegisteredUser
|
||||||
}
|
}
|
||||||
|
|
||||||
void retrieveNotRegisteredEmails()
|
async function klickTippSendFieldToUser(
|
||||||
|
events: { email: string; value: Date }[],
|
||||||
|
field: string,
|
||||||
|
): Promise<void> {
|
||||||
|
for (const event of events) {
|
||||||
|
const time = event.value.setSeconds(0)
|
||||||
|
await addFieldsToSubscriber(event.email, { [field]: Math.trunc(time / 1000) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportEventDataToKlickTipp(): Promise<boolean> {
|
||||||
|
const lastLoginEvents = await lastDateTimeEvents(EventType.USER_LOGIN)
|
||||||
|
await klickTippSendFieldToUser(lastLoginEvents, 'field186060')
|
||||||
|
|
||||||
|
const registeredEvents = await lastDateTimeEvents(EventType.USER_ACTIVATE_ACCOUNT)
|
||||||
|
await klickTippSendFieldToUser(registeredEvents, 'field186061')
|
||||||
|
|
||||||
|
const receiveTransactionEvents = await lastDateTimeEvents(EventType.TRANSACTION_RECEIVE)
|
||||||
|
await klickTippSendFieldToUser(receiveTransactionEvents, 'field185674')
|
||||||
|
|
||||||
|
const contributionCreateEvents = await lastDateTimeEvents(EventType.TRANSACTION_SEND)
|
||||||
|
await klickTippSendFieldToUser(contributionCreateEvents, 'field185673')
|
||||||
|
|
||||||
|
const linkRedeemedEvents = await lastDateTimeEvents(EventType.TRANSACTION_LINK_REDEEM)
|
||||||
|
await klickTippSendFieldToUser(linkRedeemedEvents, 'field185676')
|
||||||
|
|
||||||
|
const confirmContributionEvents = await lastDateTimeEvents(EventType.ADMIN_CONTRIBUTION_CONFIRM)
|
||||||
|
await klickTippSendFieldToUser(confirmContributionEvents, 'field185675')
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@ -66,8 +66,6 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
||||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_gradido_id`;')
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_gradido_id`;')
|
||||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_name`;')
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_name`;')
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"description": "Gradido Database Tool to execute database migrations",
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/database",
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
|||||||
@ -57,7 +57,7 @@ EMAIL_CODE_REQUEST_TIME=10
|
|||||||
WEBHOOK_ELOPAGE_SECRET=secret
|
WEBHOOK_ELOPAGE_SECRET=secret
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
FEDERATION_DHT_CONFIG_VERSION=v2.2023-02-07
|
FEDERATION_DHT_CONFIG_VERSION=v3.2023-04-26
|
||||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||||
# on an hash created from this topic
|
# on an hash created from this topic
|
||||||
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||||
|
|||||||
@ -8,6 +8,10 @@ DB_PASSWORD=$DB_PASSWORD
|
|||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
|
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
|
||||||
|
|
||||||
|
# Community
|
||||||
|
COMMUNITY_NAME=$COMMUNITY_NAME
|
||||||
|
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
||||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||||
|
|||||||
@ -6,7 +6,7 @@ module.exports = {
|
|||||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
lines: 80,
|
lines: 83,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-dht-node",
|
"name": "gradido-dht-node",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"description": "Gradido dht-node module",
|
"description": "Gradido dht-node module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/",
|
"repository": "https://github.com/gradido/gradido/",
|
||||||
@ -23,7 +23,8 @@
|
|||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "^4.1.2",
|
"tsconfig-paths": "^4.1.2",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dotenv": "^8.2.0",
|
"@types/dotenv": "^8.2.0",
|
||||||
@ -31,6 +32,7 @@
|
|||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||||
"@typescript-eslint/parser": "^5.48.0",
|
"@typescript-eslint/parser": "^5.48.0",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-config-standard": "^17.0.0",
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const constants = {
|
|||||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||||
CONFIG_VERSION: {
|
CONFIG_VERSION: {
|
||||||
DEFAULT: 'DEFAULT',
|
DEFAULT: 'DEFAULT',
|
||||||
EXPECTED: 'v2.2023-02-07',
|
EXPECTED: 'v3.2023-04-26',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -28,6 +28,12 @@ const database = {
|
|||||||
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
|
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const community = {
|
||||||
|
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
|
||||||
|
COMMUNITY_DESCRIPTION:
|
||||||
|
process.env.COMMUNITY_DESCRIPTION || 'Gradido-Community einer lokalen Entwicklungsumgebung.',
|
||||||
|
}
|
||||||
|
|
||||||
const federation = {
|
const federation = {
|
||||||
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
|
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
|
||||||
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
||||||
@ -51,6 +57,7 @@ const CONFIG = {
|
|||||||
...constants,
|
...constants,
|
||||||
...server,
|
...server,
|
||||||
...database,
|
...database,
|
||||||
|
...community,
|
||||||
...federation,
|
...federation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import { startDHT } from './index'
|
|||||||
import DHT from '@hyperswarm/dht'
|
import DHT from '@hyperswarm/dht'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import { logger } from '@test/testSetup'
|
import { logger } from '@test/testSetup'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { testEnvironment, cleanDB } from '@test/helpers'
|
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||||
|
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||||
|
|
||||||
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
||||||
|
|
||||||
@ -114,6 +116,9 @@ describe('federation', () => {
|
|||||||
const hashSpy = jest.spyOn(DHT, 'hash')
|
const hashSpy = jest.spyOn(DHT, 'hash')
|
||||||
const keyPairSpy = jest.spyOn(DHT, 'keyPair')
|
const keyPairSpy = jest.spyOn(DHT, 'keyPair')
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
CONFIG.FEDERATION_COMMUNITY_URL = 'https://test.gradido.net'
|
||||||
|
CONFIG.COMMUNITY_NAME = 'Gradido Test Community'
|
||||||
|
CONFIG.COMMUNITY_DESCRIPTION = 'Community to test the federation'
|
||||||
DHT.mockClear()
|
DHT.mockClear()
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await cleanDB()
|
await cleanDB()
|
||||||
@ -132,6 +137,64 @@ describe('federation', () => {
|
|||||||
expect(DHT).toBeCalledWith({ keyPair: keyPairMock })
|
expect(DHT).toBeCalledWith({ keyPair: keyPairMock })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stores the home community in community table ', async () => {
|
||||||
|
const result = await DbCommunity.find()
|
||||||
|
expect(result).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
url: 'https://test.gradido.net/api/',
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
communityUuid: expect.any(String),
|
||||||
|
authenticatedAt: null,
|
||||||
|
name: 'Gradido Test Community',
|
||||||
|
description: 'Community to test the federation',
|
||||||
|
creationDate: expect.any(Date),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
expect(validateUUID(result[0].communityUuid ? result[0].communityUuid : '')).toEqual(true)
|
||||||
|
expect(versionUUID(result[0].communityUuid ? result[0].communityUuid : '')).toEqual(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates 3 entries in table federated_communities', async () => {
|
||||||
|
const result = await DbFederatedCommunity.find({ order: { id: 'ASC' } })
|
||||||
|
await expect(result).toHaveLength(3)
|
||||||
|
await expect(result).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
apiVersion: '1_0',
|
||||||
|
endPoint: 'https://test.gradido.net/api/',
|
||||||
|
lastAnnouncedAt: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
apiVersion: '1_1',
|
||||||
|
endPoint: 'https://test.gradido.net/api/',
|
||||||
|
lastAnnouncedAt: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
apiVersion: '2_0',
|
||||||
|
endPoint: 'https://test.gradido.net/api/',
|
||||||
|
lastAnnouncedAt: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
describe('DHT node', () => {
|
describe('DHT node', () => {
|
||||||
it('creates a server', () => {
|
it('creates a server', () => {
|
||||||
expect(nodeCreateServerMock).toBeCalled()
|
expect(nodeCreateServerMock).toBeCalled()
|
||||||
@ -780,21 +843,21 @@ describe('federation', () => {
|
|||||||
socketEventMocks.open()
|
socketEventMocks.open()
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('calls socket write with own api versions', () => {
|
it('calls socket write with own api versions', () => {
|
||||||
expect(socketWriteMock).toBeCalledWith(
|
expect(socketWriteMock).toBeCalledWith(
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{
|
{
|
||||||
api: '1_0',
|
api: '1_0',
|
||||||
url: 'http://localhost/api/',
|
url: 'https://test.gradido.net/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: '1_1',
|
api: '1_1',
|
||||||
url: 'http://localhost/api/',
|
url: 'https://test.gradido.net/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: '2_0',
|
api: '2_0',
|
||||||
url: 'http://localhost/api/',
|
url: 'https://test.gradido.net/api/',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
@ -804,5 +867,101 @@ describe('federation', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('restart DHT', () => {
|
||||||
|
let homeCommunity: DbCommunity
|
||||||
|
let federatedCommunities: DbFederatedCommunity[]
|
||||||
|
|
||||||
|
describe('without changes', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
DHT.mockClear()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
homeCommunity = (await DbCommunity.find())[0]
|
||||||
|
federatedCommunities = await DbFederatedCommunity.find({ order: { id: 'ASC' } })
|
||||||
|
await startDHT(TEST_TOPIC)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not change home community in community table except updated at column ', async () => {
|
||||||
|
await expect(DbCommunity.find()).resolves.toEqual([
|
||||||
|
{
|
||||||
|
...homeCommunity,
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rewrites the 3 entries in table federated_communities', async () => {
|
||||||
|
const result = await DbFederatedCommunity.find()
|
||||||
|
await expect(result).toHaveLength(3)
|
||||||
|
await expect(result).toEqual([
|
||||||
|
{
|
||||||
|
...federatedCommunities[0],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[1],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[2],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('changeing URL, name and description', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
CONFIG.FEDERATION_COMMUNITY_URL = 'https://test2.gradido.net'
|
||||||
|
CONFIG.COMMUNITY_NAME = 'Second Gradido Test Community'
|
||||||
|
CONFIG.COMMUNITY_DESCRIPTION = 'Another Community to test the federation'
|
||||||
|
DHT.mockClear()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
homeCommunity = (await DbCommunity.find())[0]
|
||||||
|
federatedCommunities = await DbFederatedCommunity.find({ order: { id: 'ASC' } })
|
||||||
|
await startDHT(TEST_TOPIC)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates URL, name, description and updated at columns ', async () => {
|
||||||
|
await expect(DbCommunity.find()).resolves.toEqual([
|
||||||
|
{
|
||||||
|
...homeCommunity,
|
||||||
|
url: 'https://test2.gradido.net/api/',
|
||||||
|
name: 'Second Gradido Test Community',
|
||||||
|
description: 'Another Community to test the federation',
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rewrites the 3 entries in table federated_communities with new endpoint', async () => {
|
||||||
|
const result = await DbFederatedCommunity.find()
|
||||||
|
await expect(result).toHaveLength(3)
|
||||||
|
await expect(result).toEqual([
|
||||||
|
{
|
||||||
|
...federatedCommunities[0],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
endPoint: 'https://test2.gradido.net/api/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[1],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
endPoint: 'https://test2.gradido.net/api/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[2],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
endPoint: 'https://test2.gradido.net/api/',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,10 +4,15 @@ import DHT from '@hyperswarm/dht'
|
|||||||
import { logger } from '@/server/logger'
|
import { logger } from '@/server/logger'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
const KEY_SECRET_SEEDBYTES = 32
|
const KEY_SECRET_SEEDBYTES = 32
|
||||||
const getSeed = (): Buffer | null =>
|
const getSeed = (): Buffer | null => {
|
||||||
CONFIG.FEDERATION_DHT_SEED ? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED) : null
|
return CONFIG.FEDERATION_DHT_SEED
|
||||||
|
? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
const POLLTIME = 20000
|
const POLLTIME = 20000
|
||||||
const SUCCESSTIME = 120000
|
const SUCCESSTIME = 120000
|
||||||
@ -28,10 +33,12 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
try {
|
try {
|
||||||
const TOPIC = DHT.hash(Buffer.from(topic))
|
const TOPIC = DHT.hash(Buffer.from(topic))
|
||||||
const keyPair = DHT.keyPair(getSeed())
|
const keyPair = DHT.keyPair(getSeed())
|
||||||
logger.info(`keyPairDHT: publicKey=${keyPair.publicKey.toString('hex')}`)
|
const pubKeyString = keyPair.publicKey.toString('hex')
|
||||||
|
logger.info(`keyPairDHT: publicKey=${pubKeyString}`)
|
||||||
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
||||||
|
await writeHomeCommunityEntry(pubKeyString)
|
||||||
|
|
||||||
const ownApiVersions = await writeFederatedHomeCommunityEnries(keyPair.publicKey)
|
const ownApiVersions = await writeFederatedHomeCommunityEntries(pubKeyString)
|
||||||
logger.info(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
logger.info(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
||||||
|
|
||||||
const node = new DHT({ keyPair })
|
const node = new DHT({ keyPair })
|
||||||
@ -138,7 +145,7 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
data.peers.forEach((peer: any) => {
|
data.peers.forEach((peer: any) => {
|
||||||
const pubKey = peer.publicKey.toString('hex')
|
const pubKey = peer.publicKey.toString('hex')
|
||||||
if (
|
if (
|
||||||
pubKey !== keyPair.publicKey.toString('hex') &&
|
pubKey !== pubKeyString &&
|
||||||
!successfulRequests.includes(pubKey) &&
|
!successfulRequests.includes(pubKey) &&
|
||||||
!errorfulRequests.includes(pubKey) &&
|
!errorfulRequests.includes(pubKey) &&
|
||||||
!collectedPubKeys.includes(pubKey)
|
!collectedPubKeys.includes(pubKey)
|
||||||
@ -179,7 +186,7 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeFederatedHomeCommunityEnries(pubKey: any): Promise<CommunityApi[]> {
|
async function writeFederatedHomeCommunityEntries(pubKey: string): Promise<CommunityApi[]> {
|
||||||
const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) {
|
const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) {
|
||||||
const comApi: CommunityApi = {
|
const comApi: CommunityApi = {
|
||||||
api: apiEnum,
|
api: apiEnum,
|
||||||
@ -189,21 +196,65 @@ async function writeFederatedHomeCommunityEnries(pubKey: any): Promise<Community
|
|||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
// first remove privious existing homeCommunity entries
|
// first remove privious existing homeCommunity entries
|
||||||
DbFederatedCommunity.createQueryBuilder().delete().where({ foreign: false }).execute()
|
await DbFederatedCommunity.createQueryBuilder().delete().where({ foreign: false }).execute()
|
||||||
|
for (const homeApiVersion of homeApiVersions) {
|
||||||
homeApiVersions.forEach(async function (homeApi) {
|
const homeCom = DbFederatedCommunity.create()
|
||||||
const homeCom = new DbFederatedCommunity()
|
|
||||||
homeCom.foreign = false
|
homeCom.foreign = false
|
||||||
homeCom.apiVersion = homeApi.api
|
homeCom.apiVersion = homeApiVersion.api
|
||||||
homeCom.endPoint = homeApi.url
|
homeCom.endPoint = homeApiVersion.url
|
||||||
homeCom.publicKey = pubKey.toString('hex')
|
homeCom.publicKey = Buffer.from(pubKey)
|
||||||
|
|
||||||
// this will NOT update the updatedAt column, to distingue between a normal update and the last announcement
|
|
||||||
await DbFederatedCommunity.insert(homeCom)
|
await DbFederatedCommunity.insert(homeCom)
|
||||||
logger.info(`federation home-community inserted successfully: ${JSON.stringify(homeCom)}`)
|
logger.info(`federation home-community inserted successfully:`, homeApiVersion)
|
||||||
})
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Federation: Error writing HomeCommunity-Entries: ${err}`)
|
throw new Error(`Federation: Error writing federated HomeCommunity-Entries: ${err}`)
|
||||||
}
|
}
|
||||||
return homeApiVersions
|
return homeApiVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeHomeCommunityEntry(pubKey: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// check for existing homeCommunity entry
|
||||||
|
let homeCom = await DbCommunity.findOne({
|
||||||
|
foreign: false,
|
||||||
|
publicKey: Buffer.from(pubKey),
|
||||||
|
})
|
||||||
|
if (!homeCom) {
|
||||||
|
// check if a homecommunity with a different publicKey still exists
|
||||||
|
homeCom = await DbCommunity.findOne({ foreign: false })
|
||||||
|
}
|
||||||
|
if (homeCom) {
|
||||||
|
// simply update the existing entry, but it MUST keep the ID and UUID because of possible relations
|
||||||
|
homeCom.publicKey = Buffer.from(pubKey)
|
||||||
|
homeCom.url = CONFIG.FEDERATION_COMMUNITY_URL + '/api/'
|
||||||
|
homeCom.name = CONFIG.COMMUNITY_NAME
|
||||||
|
homeCom.description = CONFIG.COMMUNITY_DESCRIPTION
|
||||||
|
await DbCommunity.save(homeCom)
|
||||||
|
logger.info(`home-community updated successfully:`, homeCom)
|
||||||
|
} else {
|
||||||
|
// insert a new homecommunity entry including a new ID and a new but ensured unique UUID
|
||||||
|
homeCom = new DbCommunity()
|
||||||
|
homeCom.foreign = false
|
||||||
|
homeCom.publicKey = Buffer.from(pubKey)
|
||||||
|
homeCom.communityUuid = await newCommunityUuid()
|
||||||
|
homeCom.url = CONFIG.FEDERATION_COMMUNITY_URL + '/api/'
|
||||||
|
homeCom.name = CONFIG.COMMUNITY_NAME
|
||||||
|
homeCom.description = CONFIG.COMMUNITY_DESCRIPTION
|
||||||
|
homeCom.creationDate = new Date()
|
||||||
|
await DbCommunity.insert(homeCom)
|
||||||
|
logger.info(`home-community inserted successfully:`, homeCom)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Federation: Error writing HomeCommunity-Entry: ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCommunityUuid = async (): Promise<string> => {
|
||||||
|
while (true) {
|
||||||
|
const communityUuid = uuidv4()
|
||||||
|
if ((await DbCommunity.count({ where: { communityUuid } })) === 0) {
|
||||||
|
return communityUuid
|
||||||
|
}
|
||||||
|
logger.info('CommunityUuid creation conflict...', communityUuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,9 +21,8 @@ async function main() {
|
|||||||
logger.fatal('Fatal: Database Version incorrect')
|
logger.fatal('Fatal: Database Version incorrect')
|
||||||
throw new Error('Fatal: Database Version incorrect')
|
throw new Error('Fatal: Database Version incorrect')
|
||||||
}
|
}
|
||||||
|
logger.debug(`dhtseed set by CONFIG.FEDERATION_DHT_SEED=${CONFIG.FEDERATION_DHT_SEED}`)
|
||||||
// eslint-disable-next-line no-console
|
logger.info(
|
||||||
console.log(
|
|
||||||
`starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${
|
`starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${
|
||||||
CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...'
|
CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...'
|
||||||
}`,
|
}`,
|
||||||
|
|||||||
@ -22,8 +22,8 @@ const context = {
|
|||||||
|
|
||||||
export const cleanDB = async () => {
|
export const cleanDB = async () => {
|
||||||
// this only works as long we do not have foreign key constraints
|
// this only works as long we do not have foreign key constraints
|
||||||
for (let i = 0; i < entities.length; i++) {
|
for (const entity of entities) {
|
||||||
await resetEntity(entities[i])
|
await resetEntity(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -769,6 +769,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||||
|
|
||||||
|
"@types/uuid@^8.3.4":
|
||||||
|
version "8.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "21.0.0"
|
version "21.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
||||||
@ -4138,6 +4143,11 @@ url-parse@^1.5.3:
|
|||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
|
uuid@^8.3.2:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
v8-compile-cache-lib@^3.0.1:
|
v8-compile-cache-lib@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-federation",
|
"name": "gradido-federation",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/federation",
|
"repository": "https://github.com/gradido/gradido/federation",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bootstrap-vue-gradido-wallet",
|
"name": "bootstrap-vue-gradido-wallet",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node run/server.js",
|
"start": "node run/server.js",
|
||||||
@ -50,6 +50,7 @@
|
|||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"qrcanvas-vue": "2.1.1",
|
"qrcanvas-vue": "2.1.1",
|
||||||
"regenerator-runtime": "^0.13.7",
|
"regenerator-runtime": "^0.13.7",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vee-validate": "^3.4.5",
|
"vee-validate": "^3.4.5",
|
||||||
"vue": "2.6.12",
|
"vue": "2.6.12",
|
||||||
"vue-apollo": "^3.0.7",
|
"vue-apollo": "^3.0.7",
|
||||||
|
|||||||
@ -71,9 +71,9 @@ describe('TransactionForm', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('with balance <= 0.00 GDD the form is disabled', () => {
|
describe('with balance <= 0.00 GDD the form is disabled', () => {
|
||||||
it('has a disabled input field of type email', () => {
|
it('has a disabled input field of type text', () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('div[data-test="input-email"]').find('input').attributes('disabled'),
|
wrapper.find('div[data-test="input-identifier"]').find('input').attributes('disabled'),
|
||||||
).toBe('disabled')
|
).toBe('disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -116,51 +116,54 @@ describe('TransactionForm', () => {
|
|||||||
expect(wrapper.vm.radioSelected).toBe(SEND_TYPES.send)
|
expect(wrapper.vm.radioSelected).toBe(SEND_TYPES.send)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('email field', () => {
|
describe('identifier field', () => {
|
||||||
it('has an input field of type email', () => {
|
it('has an input field of type text', () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('div[data-test="input-email"]').find('input').attributes('type'),
|
wrapper.find('div[data-test="input-identifier"]').find('input').attributes('type'),
|
||||||
).toBe('email')
|
).toBe('text')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a label form.receiver', () => {
|
it('has a label form.recipient', () => {
|
||||||
expect(wrapper.find('div[data-test="input-email"]').find('label').text()).toBe(
|
expect(wrapper.find('div[data-test="input-identifier"]').find('label').text()).toBe(
|
||||||
'form.recipient',
|
'form.recipient',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a placeholder "E-Mail"', () => {
|
it('has a placeholder for identifier', () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('div[data-test="input-email"]').find('input').attributes('placeholder'),
|
wrapper
|
||||||
).toBe('form.email')
|
.find('div[data-test="input-identifier"]')
|
||||||
|
.find('input')
|
||||||
|
.attributes('placeholder'),
|
||||||
|
).toBe('form.identifier')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('flushes an error message when no valid email is given', async () => {
|
it('flushes an error message when no valid identifier is given', async () => {
|
||||||
await wrapper.find('div[data-test="input-email"]').find('input').setValue('a')
|
await wrapper.find('div[data-test="input-identifier"]').find('input').setValue('a')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('div[data-test="input-email"]').find('.invalid-feedback').text(),
|
wrapper.find('div[data-test="input-identifier"]').find('.invalid-feedback').text(),
|
||||||
).toBe('validations.messages.email')
|
).toBe('form.validation.valid-identifier')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO:SKIPPED there is no check that the email being sent to is the same as the user's email.
|
// TODO:SKIPPED there is no check that the email being sent to is the same as the user's email.
|
||||||
it.skip('flushes an error message when email is the email of logged in user', async () => {
|
it.skip('flushes an error message when email is the email of logged in user', async () => {
|
||||||
await wrapper
|
await wrapper
|
||||||
.find('div[data-test="input-email"]')
|
.find('div[data-test="input-identifier"]')
|
||||||
.find('input')
|
.find('input')
|
||||||
.setValue('user@example.org')
|
.setValue('user@example.org')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('div[data-test="input-email"]').find('.invalid-feedback').text(),
|
wrapper.find('div[data-test="input-identifier"]').find('.invalid-feedback').text(),
|
||||||
).toBe('form.validation.is-not')
|
).toBe('form.validation.is-not')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('trims the email after blur', async () => {
|
it('trims the identifier after blur', async () => {
|
||||||
await wrapper
|
await wrapper
|
||||||
.find('div[data-test="input-email"]')
|
.find('div[data-test="input-identifier"]')
|
||||||
.find('input')
|
.find('input')
|
||||||
.setValue(' valid@email.com ')
|
.setValue(' valid@email.com ')
|
||||||
await wrapper.find('div[data-test="input-email"]').find('input').trigger('blur')
|
await wrapper.find('div[data-test="input-identifier"]').find('input').trigger('blur')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.vm.form.identifier).toBe('valid@email.com')
|
expect(wrapper.vm.form.identifier).toBe('valid@email.com')
|
||||||
})
|
})
|
||||||
@ -304,7 +307,7 @@ Die ganze Welt bezwingen.“`)
|
|||||||
|
|
||||||
it('clears all fields on click', async () => {
|
it('clears all fields on click', async () => {
|
||||||
await wrapper
|
await wrapper
|
||||||
.find('div[data-test="input-email"]')
|
.find('div[data-test="input-identifier"]')
|
||||||
.find('input')
|
.find('input')
|
||||||
.setValue('someone@watches.tv')
|
.setValue('someone@watches.tv')
|
||||||
await wrapper.find('div[data-test="input-amount"]').find('input').setValue('87.23')
|
await wrapper.find('div[data-test="input-amount"]').find('input').setValue('87.23')
|
||||||
@ -327,7 +330,7 @@ Die ganze Welt bezwingen.“`)
|
|||||||
describe('submit', () => {
|
describe('submit', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper
|
await wrapper
|
||||||
.find('div[data-test="input-email"]')
|
.find('div[data-test="input-identifier"]')
|
||||||
.find('input')
|
.find('input')
|
||||||
.setValue('someone@watches.tv')
|
.setValue('someone@watches.tv')
|
||||||
await wrapper.find('div[data-test="input-amount"]').find('input').setValue('87.23')
|
await wrapper.find('div[data-test="input-amount"]').find('input').setValue('87.23')
|
||||||
@ -380,8 +383,8 @@ Die ganze Welt bezwingen.“`)
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('query for username with success', () => {
|
describe('query for username with success', () => {
|
||||||
it('has no email input field', () => {
|
it('has no identifier input field', () => {
|
||||||
expect(wrapper.find('div[data-test="input-email"]').exists()).toBe(false)
|
expect(wrapper.find('div[data-test="input-identifier"]').exists()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('queries the username', () => {
|
it('queries the username', () => {
|
||||||
|
|||||||
@ -59,10 +59,10 @@
|
|||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="12" v-if="radioSelected === sendTypes.send">
|
<b-col cols="12" v-if="radioSelected === sendTypes.send">
|
||||||
<div v-if="!gradidoID">
|
<div v-if="!gradidoID">
|
||||||
<input-email
|
<input-identifier
|
||||||
:name="$t('form.recipient')"
|
:name="$t('form.recipient')"
|
||||||
:label="$t('form.recipient')"
|
:label="$t('form.recipient')"
|
||||||
:placeholder="$t('form.email')"
|
:placeholder="$t('form.identifier')"
|
||||||
v-model="form.identifier"
|
v-model="form.identifier"
|
||||||
:disabled="isBalanceDisabled"
|
:disabled="isBalanceDisabled"
|
||||||
@onValidation="onValidation"
|
@onValidation="onValidation"
|
||||||
@ -134,7 +134,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { SEND_TYPES } from '@/pages/Send'
|
import { SEND_TYPES } from '@/pages/Send'
|
||||||
import InputEmail from '@/components/Inputs/InputEmail'
|
import InputIdentifier from '@/components/Inputs/InputIdentifier'
|
||||||
import InputAmount from '@/components/Inputs/InputAmount'
|
import InputAmount from '@/components/Inputs/InputAmount'
|
||||||
import InputTextarea from '@/components/Inputs/InputTextarea'
|
import InputTextarea from '@/components/Inputs/InputTextarea'
|
||||||
import { user as userQuery } from '@/graphql/queries'
|
import { user as userQuery } from '@/graphql/queries'
|
||||||
@ -144,7 +144,7 @@ import { COMMUNITY_NAME } from '@/config'
|
|||||||
export default {
|
export default {
|
||||||
name: 'TransactionForm',
|
name: 'TransactionForm',
|
||||||
components: {
|
components: {
|
||||||
InputEmail,
|
InputIdentifier,
|
||||||
InputAmount,
|
InputAmount,
|
||||||
InputTextarea,
|
InputTextarea,
|
||||||
},
|
},
|
||||||
|
|||||||
68
frontend/src/components/Inputs/InputIdentifier.vue
Normal file
68
frontend/src/components/Inputs/InputIdentifier.vue
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<validation-provider
|
||||||
|
tag="div"
|
||||||
|
:rules="rules"
|
||||||
|
:name="name"
|
||||||
|
v-slot="{ errors, valid, validated, ariaInput, ariaMsg }"
|
||||||
|
>
|
||||||
|
<b-form-group :label="label" :label-for="labelFor" data-test="input-identifier">
|
||||||
|
<b-form-input
|
||||||
|
v-model="currentValue"
|
||||||
|
v-bind="ariaInput"
|
||||||
|
:id="labelFor"
|
||||||
|
:name="name"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
type="text"
|
||||||
|
:state="validated ? valid : false"
|
||||||
|
trim
|
||||||
|
class="bg-248"
|
||||||
|
:disabled="disabled"
|
||||||
|
autocomplete="off"
|
||||||
|
></b-form-input>
|
||||||
|
<b-form-invalid-feedback v-bind="ariaMsg">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</b-form-invalid-feedback>
|
||||||
|
</b-form-group>
|
||||||
|
</validation-provider>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'InputEmail',
|
||||||
|
props: {
|
||||||
|
rules: {
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
required: true,
|
||||||
|
validIdentifier: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: { type: String, required: true },
|
||||||
|
label: { type: String, required: true },
|
||||||
|
placeholder: { type: String, required: true },
|
||||||
|
value: { type: String, required: true },
|
||||||
|
disabled: { type: Boolean, required: false, default: false },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentValue: this.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
labelFor() {
|
||||||
|
return this.name + '-input-field'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentValue() {
|
||||||
|
this.$emit('input', this.currentValue)
|
||||||
|
},
|
||||||
|
value() {
|
||||||
|
if (this.value !== this.currentValue) {
|
||||||
|
this.currentValue = this.value
|
||||||
|
}
|
||||||
|
this.$emit('onValidation')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -29,10 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div data-test="navbar-item-username">{{ username.username }}</div>
|
<div data-test="navbar-item-username">{{ username.username }}</div>
|
||||||
|
<div data-test="navbar-item-email">{{ $store.state.email }}</div>
|
||||||
<div data-test="navbar-item-email">
|
|
||||||
{{ $store.state.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
@ -142,6 +142,7 @@
|
|||||||
"from": "Von",
|
"from": "Von",
|
||||||
"generate_now": "Jetzt generieren",
|
"generate_now": "Jetzt generieren",
|
||||||
"hours": "Stunden",
|
"hours": "Stunden",
|
||||||
|
"identifier": "Email, Nutzername oder Gradido ID",
|
||||||
"lastname": "Nachname",
|
"lastname": "Nachname",
|
||||||
"memo": "Nachricht",
|
"memo": "Nachricht",
|
||||||
"message": "Nachricht",
|
"message": "Nachricht",
|
||||||
@ -175,7 +176,8 @@
|
|||||||
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
|
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
|
||||||
"username-allowed-chars": "Der Nutzername darf nur aus Buchstaben (ohne Umlaute), Zahlen, Binde- oder Unterstrichen bestehen.",
|
"username-allowed-chars": "Der Nutzername darf nur aus Buchstaben (ohne Umlaute), Zahlen, Binde- oder Unterstrichen bestehen.",
|
||||||
"username-hyphens": "Binde- oder Unterstriche müssen zwischen Buchstaben oder Zahlen stehen.",
|
"username-hyphens": "Binde- oder Unterstriche müssen zwischen Buchstaben oder Zahlen stehen.",
|
||||||
"username-unique": "Der Nutzername ist bereits vergeben."
|
"username-unique": "Der Nutzername ist bereits vergeben.",
|
||||||
|
"valid-identifier": "Muss eine Email, ein Nutzernamen oder eine gradido ID sein."
|
||||||
},
|
},
|
||||||
"your_amount": "Dein Betrag"
|
"your_amount": "Dein Betrag"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -142,6 +142,7 @@
|
|||||||
"from": "from",
|
"from": "from",
|
||||||
"generate_now": "Generate now",
|
"generate_now": "Generate now",
|
||||||
"hours": "Hours",
|
"hours": "Hours",
|
||||||
|
"identifier": "Email, username or gradido ID",
|
||||||
"lastname": "Lastname",
|
"lastname": "Lastname",
|
||||||
"memo": "Message",
|
"memo": "Message",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
@ -175,7 +176,8 @@
|
|||||||
"is-not": "You cannot send Gradidos to yourself",
|
"is-not": "You cannot send Gradidos to yourself",
|
||||||
"username-allowed-chars": "The username may only contain letters, numbers, hyphens or underscores.",
|
"username-allowed-chars": "The username may only contain letters, numbers, hyphens or underscores.",
|
||||||
"username-hyphens": "Hyphens or underscores must be in between letters or numbers.",
|
"username-hyphens": "Hyphens or underscores must be in between letters or numbers.",
|
||||||
"username-unique": "This username is already taken."
|
"username-unique": "This username is already taken.",
|
||||||
|
"valid-identifier": "Must be a valid email, username or gradido ID."
|
||||||
},
|
},
|
||||||
"your_amount": "Your amount"
|
"your_amount": "Your amount"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -146,6 +146,10 @@ describe('Login', () => {
|
|||||||
expect(mockStoreDispach).toBeCalledWith('login', 'token')
|
expect(mockStoreDispach).toBeCalledWith('login', 'token')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('commits email to store', () => {
|
||||||
|
expect(mockStoreCommit).toBeCalledWith('email', 'user@example.org')
|
||||||
|
})
|
||||||
|
|
||||||
it('hides the spinner', () => {
|
it('hides the spinner', () => {
|
||||||
expect(spinnerHideMock).toBeCalled()
|
expect(spinnerHideMock).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -100,6 +100,7 @@ export default {
|
|||||||
data: { login },
|
data: { login },
|
||||||
} = result
|
} = result
|
||||||
this.$store.dispatch('login', login)
|
this.$store.dispatch('login', login)
|
||||||
|
this.$store.commit('email', this.form.email)
|
||||||
await loader.hide()
|
await loader.hide()
|
||||||
if (this.$route.params.code) {
|
if (this.$route.params.code) {
|
||||||
this.$router.push(`/redeem/${this.$route.params.code}`)
|
this.$router.push(`/redeem/${this.$route.params.code}`)
|
||||||
|
|||||||
@ -66,8 +66,11 @@ describe('Send', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
||||||
await transactionForm.findAll('input[type="radio"]').at(0).setChecked()
|
await transactionForm.findAll('input[type="radio"]').at(0).setChecked()
|
||||||
await transactionForm.find('input[type="email"]').setValue('user@example.org')
|
await transactionForm
|
||||||
await transactionForm.find('input[type="text"]').setValue('23.45')
|
.find('[data-test="input-identifier"]')
|
||||||
|
.find('input')
|
||||||
|
.setValue('user@example.org')
|
||||||
|
await transactionForm.find('[data-test="input-amount"]').find('input').setValue('23.45')
|
||||||
await transactionForm.find('textarea').setValue('Make the best of it!')
|
await transactionForm.find('textarea').setValue('Make the best of it!')
|
||||||
await transactionForm.find('form').trigger('submit')
|
await transactionForm.find('form').trigger('submit')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
@ -91,8 +94,12 @@ describe('Send', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('restores the previous data in the formular', () => {
|
it('restores the previous data in the formular', () => {
|
||||||
expect(wrapper.find("input[type='email']").vm.$el.value).toBe('user@example.org')
|
expect(wrapper.find('[data-test="input-identifier"]').find('input').vm.$el.value).toBe(
|
||||||
expect(wrapper.find("input[type='text']").vm.$el.value).toBe('23.45')
|
'user@example.org',
|
||||||
|
)
|
||||||
|
expect(wrapper.find('[data-test="input-amount"]').find('input').vm.$el.value).toBe(
|
||||||
|
'23.45',
|
||||||
|
)
|
||||||
expect(wrapper.find('textarea').vm.$el.value).toBe('Make the best of it!')
|
expect(wrapper.find('textarea').vm.$el.value).toBe('Make the best of it!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -175,7 +182,10 @@ describe('Send', () => {
|
|||||||
|
|
||||||
it('has no email input field', () => {
|
it('has no email input field', () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper.findComponent({ name: 'TransactionForm' }).find('input[type="email"]').exists(),
|
wrapper
|
||||||
|
.findComponent({ name: 'TransactionForm' })
|
||||||
|
.find('[data-test="input-identifier"]')
|
||||||
|
.exists(),
|
||||||
).toBe(false)
|
).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -183,7 +193,7 @@ describe('Send', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
||||||
await transactionForm.find('input[type="text"]').setValue('34.56')
|
await transactionForm.find('[data-test="input-amount"]').find('input').setValue('34.56')
|
||||||
await transactionForm.find('textarea').setValue('Make the best of it!')
|
await transactionForm.find('textarea').setValue('Make the best of it!')
|
||||||
await transactionForm.find('form').trigger('submit')
|
await transactionForm.find('form').trigger('submit')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
@ -243,7 +253,7 @@ describe('Send', () => {
|
|||||||
})
|
})
|
||||||
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
||||||
await transactionForm.findAll('input[type="radio"]').at(1).setChecked()
|
await transactionForm.findAll('input[type="radio"]').at(1).setChecked()
|
||||||
await transactionForm.find('input[type="text"]').setValue('56.78')
|
await transactionForm.find('[data-test="input-amount"]').find('input').setValue('56.78')
|
||||||
await transactionForm.find('textarea').setValue('Make the best of the link!')
|
await transactionForm.find('textarea').setValue('Make the best of the link!')
|
||||||
await transactionForm.find('form').trigger('submit')
|
await transactionForm.find('form').trigger('submit')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|||||||
@ -53,6 +53,9 @@ export const mutations = {
|
|||||||
hideAmountGDT: (state, hideAmountGDT) => {
|
hideAmountGDT: (state, hideAmountGDT) => {
|
||||||
state.hideAmountGDT = !!hideAmountGDT
|
state.hideAmountGDT = !!hideAmountGDT
|
||||||
},
|
},
|
||||||
|
email: (state, email) => {
|
||||||
|
state.email = email || ''
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
@ -81,6 +84,7 @@ export const actions = {
|
|||||||
commit('isAdmin', false)
|
commit('isAdmin', false)
|
||||||
commit('hideAmountGDD', false)
|
commit('hideAmountGDD', false)
|
||||||
commit('hideAmountGDT', true)
|
commit('hideAmountGDT', true)
|
||||||
|
commit('email', '')
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -109,6 +113,7 @@ try {
|
|||||||
publisherId: null,
|
publisherId: null,
|
||||||
hideAmountGDD: null,
|
hideAmountGDD: null,
|
||||||
hideAmountGDT: null,
|
hideAmountGDT: null,
|
||||||
|
email: '',
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
// Syncronous mutation of the state
|
// Syncronous mutation of the state
|
||||||
|
|||||||
@ -33,6 +33,7 @@ const {
|
|||||||
hasElopage,
|
hasElopage,
|
||||||
hideAmountGDD,
|
hideAmountGDD,
|
||||||
hideAmountGDT,
|
hideAmountGDT,
|
||||||
|
email,
|
||||||
} = mutations
|
} = mutations
|
||||||
const { login, logout } = actions
|
const { login, logout } = actions
|
||||||
|
|
||||||
@ -166,6 +167,14 @@ describe('Vuex store', () => {
|
|||||||
expect(state.hideAmountGDT).toEqual(true)
|
expect(state.hideAmountGDT).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('email', () => {
|
||||||
|
it('sets the state of email', () => {
|
||||||
|
const state = { email: '' }
|
||||||
|
email(state, 'peter@luatig.de')
|
||||||
|
expect(state.email).toEqual('peter@luatig.de')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('actions', () => {
|
describe('actions', () => {
|
||||||
@ -253,9 +262,9 @@ describe('Vuex store', () => {
|
|||||||
const commit = jest.fn()
|
const commit = jest.fn()
|
||||||
const state = {}
|
const state = {}
|
||||||
|
|
||||||
it('calls eleven commits', () => {
|
it('calls twelve commits', () => {
|
||||||
logout({ commit, state })
|
logout({ commit, state })
|
||||||
expect(commit).toHaveBeenCalledTimes(11)
|
expect(commit).toHaveBeenCalledTimes(12)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits token', () => {
|
it('commits token', () => {
|
||||||
@ -312,6 +321,12 @@ describe('Vuex store', () => {
|
|||||||
logout({ commit, state })
|
logout({ commit, state })
|
||||||
expect(commit).toHaveBeenNthCalledWith(11, 'hideAmountGDT', true)
|
expect(commit).toHaveBeenNthCalledWith(11, 'hideAmountGDT', true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('commits email', () => {
|
||||||
|
logout({ commit, state })
|
||||||
|
expect(commit).toHaveBeenNthCalledWith(12, 'email', '')
|
||||||
|
})
|
||||||
|
|
||||||
// how to get this working?
|
// how to get this working?
|
||||||
it.skip('calls localStorage.clear()', () => {
|
it.skip('calls localStorage.clear()', () => {
|
||||||
const clearStorageMock = jest.fn()
|
const clearStorageMock = jest.fn()
|
||||||
|
|||||||
@ -2,6 +2,13 @@ import { configure, extend } from 'vee-validate'
|
|||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
|
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
|
||||||
import { checkUsername } from '@/graphql/queries'
|
import { checkUsername } from '@/graphql/queries'
|
||||||
|
import { validate as validateUuid, version as versionUuid } from 'uuid'
|
||||||
|
|
||||||
|
// taken from vee-validate
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
|
const USERNAME_REGEX = /^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/
|
||||||
|
|
||||||
export const loadAllRules = (i18nCallback, apollo) => {
|
export const loadAllRules = (i18nCallback, apollo) => {
|
||||||
configure({
|
configure({
|
||||||
@ -141,7 +148,7 @@ export const loadAllRules = (i18nCallback, apollo) => {
|
|||||||
|
|
||||||
extend('usernameUnique', {
|
extend('usernameUnique', {
|
||||||
validate(value) {
|
validate(value) {
|
||||||
if (!value.match(/^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/)) return true
|
if (!value.match(USERNAME_REGEX)) return true
|
||||||
return apollo
|
return apollo
|
||||||
.query({
|
.query({
|
||||||
query: checkUsername,
|
query: checkUsername,
|
||||||
@ -155,4 +162,14 @@ export const loadAllRules = (i18nCallback, apollo) => {
|
|||||||
},
|
},
|
||||||
message: (_, values) => i18nCallback.t('form.validation.username-unique', values),
|
message: (_, values) => i18nCallback.t('form.validation.username-unique', values),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
extend('validIdentifier', {
|
||||||
|
validate(value) {
|
||||||
|
const isEmail = !!EMAIL_REGEX.test(value)
|
||||||
|
const isUsername = !!value.match(USERNAME_REGEX)
|
||||||
|
const isGradidoId = validateUuid(value) && versionUuid(value) === 4
|
||||||
|
return isEmail || isUsername || isGradidoId
|
||||||
|
},
|
||||||
|
message: (_, values) => i18nCallback.t('form.validation.valid-identifier', values),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14176,6 +14176,11 @@ uuid@^8.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
uuid@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||||
|
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0:
|
v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido",
|
"name": "gradido",
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"description": "Gradido",
|
"description": "Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "git@github.com:gradido/gradido.git",
|
"repository": "git@github.com:gradido/gradido.git",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user