diff --git a/backend/src/federation/client/FederationClient_1_0.ts b/backend/src/federation/client/1_0/FederationClient.ts similarity index 92% rename from backend/src/federation/client/FederationClient_1_0.ts rename to backend/src/federation/client/1_0/FederationClient.ts index c8e878ded..ba446abe8 100644 --- a/backend/src/federation/client/FederationClient_1_0.ts +++ b/backend/src/federation/client/1_0/FederationClient.ts @@ -1,10 +1,11 @@ import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' 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' -export class FederationClient_1_0 { +// eslint-disable-next-line camelcase +export class FederationClient { dbCom: DbFederatedCommunity endpoint: string client: GraphQLClient diff --git a/backend/src/federation/query/getPublicKey.ts b/backend/src/federation/client/1_0/query/getPublicKey.ts similarity index 100% rename from backend/src/federation/query/getPublicKey.ts rename to backend/src/federation/client/1_0/query/getPublicKey.ts diff --git a/backend/src/federation/client/1_1/FederationClient.ts b/backend/src/federation/client/1_1/FederationClient.ts new file mode 100644 index 000000000..2fdfedd92 --- /dev/null +++ b/backend/src/federation/client/1_1/FederationClient.ts @@ -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 {} diff --git a/backend/src/federation/client/FederationClient.ts b/backend/src/federation/client/FederationClientFactory.ts similarity index 53% rename from backend/src/federation/client/FederationClient.ts rename to backend/src/federation/client/FederationClientFactory.ts index db1e5e3b2..d057ffd04 100644 --- a/backend/src/federation/client/FederationClient.ts +++ b/backend/src/federation/client/FederationClientFactory.ts @@ -1,21 +1,23 @@ 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 { FederationClient_1_0 } from './FederationClient_1_0' -import { FederationClient_1_1 } from './FederationClient_1_1' +// eslint-disable-next-line camelcase +type FederationClient = V1_0_FederationClient | V1_1_FederationClient -type FederationClientType = FederationClient_1_0 | FederationClient_1_1 - -interface ClientInstance { +interface FederationClientInstance { id: number // eslint-disable-next-line no-use-before-define - client: FederationClientType + client: FederationClient } // eslint-disable-next-line @typescript-eslint/no-extraneous-class -export class FederationClient { - private static instanceArray: ClientInstance[] = [] +export class FederationClientFactory { + private static instanceArray: FederationClientInstance[] = [] /** * The Singleton's constructor should always be private to prevent direct @@ -27,9 +29,9 @@ export class FederationClient { private static createFederationClient = (dbCom: DbFederatedCommunity) => { switch (dbCom.apiVersion) { case ApiVersionType.V1_0: - return new FederationClient_1_0(dbCom) + return new V1_0_FederationClient(dbCom) case ApiVersionType.V1_1: - return new FederationClient_1_1(dbCom) + return new V1_1_FederationClient(dbCom) default: return null } @@ -41,14 +43,19 @@ export class FederationClient { * This implementation let you subclass the Singleton class while keeping * just one instance of each subclass around. */ - public static getInstance(dbCom: DbFederatedCommunity): FederationClientType | null { - const instance = FederationClient.instanceArray.find((instance) => instance.id === dbCom.id) + public static getInstance(dbCom: DbFederatedCommunity): FederationClient | null { + const instance = FederationClientFactory.instanceArray.find( + (instance) => instance.id === dbCom.id, + ) if (instance) { return instance.client } - const client = FederationClient.createFederationClient(dbCom) + const client = FederationClientFactory.createFederationClient(dbCom) if (client) { - FederationClient.instanceArray.push({ id: dbCom.id, client } as ClientInstance) + FederationClientFactory.instanceArray.push({ + id: dbCom.id, + client, + } as FederationClientInstance) } return client } diff --git a/backend/src/federation/client/FederationClient_1_1.ts b/backend/src/federation/client/FederationClient_1_1.ts deleted file mode 100644 index 27679b423..000000000 --- a/backend/src/federation/client/FederationClient_1_1.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { FederationClient_1_0 } from './FederationClient_1_0' - -export class FederationClient_1_1 extends FederationClient_1_0 {} diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index cf8624be1..834f37e16 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -8,6 +8,8 @@ import { Connection } from '@dbTools/typeorm' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' 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 { logger } from '@test/testSetup' @@ -57,10 +59,23 @@ describe('validate Communities', () => { expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`) }) - describe('with one Community of api 1_0', () => { + describe('with one Community of api 1_0 and not 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: 'somePubKey', + }, + }, + } as Response + }) const variables1 = { - publicKey: Buffer.from('11111111111111111111111111111111'), + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), apiVersion: '1_0', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -89,11 +104,85 @@ describe('validate Communities', () => { '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 + }) + 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', () => { 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 + }) const variables2 = { - publicKey: Buffer.from('11111111111111111111111111111111'), + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), apiVersion: '1_1', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -109,6 +198,7 @@ describe('validate Communities', () => { }) .execute() + await DbFederatedCommunity.update({}, { verifiedAt: null }) jest.clearAllMocks() await validateCommunities() }) @@ -132,7 +222,9 @@ describe('validate Communities', () => { let dbCom: DbFederatedCommunity beforeEach(async () => { const variables3 = { - publicKey: Buffer.from('11111111111111111111111111111111'), + publicKey: Buffer.from( + '1111111111111111111111111111111111111111111111111111111111111111', + ), apiVersion: '2_0', endPoint: 'http//localhost:5001/api/', lastAnnouncedAt: new Date(), @@ -150,6 +242,7 @@ describe('validate Communities', () => { dbCom = await DbFederatedCommunity.findOneOrFail({ where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion }, }) + await DbFederatedCommunity.update({}, { verifiedAt: null }) jest.clearAllMocks() await validateCommunities() }) diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 91c85d09b..91c6ee724 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -3,9 +3,11 @@ import { IsNull } from '@dbTools/typeorm' 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 { FederationClient } from './client/FederationClient' import { ApiVersionType } from './enum/apiVersionType' export function startValidateCommunities(timerInterval: number): void { @@ -37,17 +39,20 @@ export async function validateCommunities(): Promise { continue } try { - const client = FederationClient.getInstance(dbCom) - const pubKey = await client?.getPublicKey() - if (pubKey && pubKey === dbCom.publicKey.toString()) { - await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) - logger.info('Federation: verified community', dbCom) - } else { - logger.warn( - 'Federation: received not matching publicKey:', - pubKey, - dbCom.publicKey.toString(), - ) + const client = FederationClientFactory.getInstance(dbCom) + // eslint-disable-next-line camelcase + if (client instanceof V1_0_FederationClient) { + const pubKey = await client.getPublicKey() + if (pubKey && pubKey === dbCom.publicKey.toString()) { + await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) + logger.info('Federation: verified community with', dbCom.endPoint) + } else { + logger.warn( + 'Federation: received not matching publicKey:', + pubKey, + dbCom.publicKey.toString(), + ) + } } } catch (err) { logger.error(`Error:`, err) diff --git a/deployment/bare_metal/.env.dist b/deployment/bare_metal/.env.dist index f20a8c2d1..2c237d22c 100644 --- a/deployment/bare_metal/.env.dist +++ b/deployment/bare_metal/.env.dist @@ -57,7 +57,7 @@ EMAIL_CODE_REQUEST_TIME=10 WEBHOOK_ELOPAGE_SECRET=secret # 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 # on an hash created from this topic # FEDERATION_DHT_TOPIC=GRADIDO_HUB diff --git a/dht-node/.env.template b/dht-node/.env.template index efe6158a6..a7603c15a 100644 --- a/dht-node/.env.template +++ b/dht-node/.env.template @@ -8,6 +8,10 @@ DB_PASSWORD=$DB_PASSWORD DB_DATABASE=gradido_community TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH +# Community +COMMUNITY_NAME=$COMMUNITY_NAME +COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION + # Federation 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 diff --git a/dht-node/jest.config.js b/dht-node/jest.config.js index fa00ed868..0b83d8edd 100644 --- a/dht-node/jest.config.js +++ b/dht-node/jest.config.js @@ -6,7 +6,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 80, + lines: 83, }, }, setupFiles: ['/test/testSetup.ts'], diff --git a/dht-node/package.json b/dht-node/package.json index 13b9f75dd..d5dae0230 100644 --- a/dht-node/package.json +++ b/dht-node/package.json @@ -23,7 +23,8 @@ "nodemon": "^2.0.20", "ts-node": "^10.9.1", "tsconfig-paths": "^4.1.2", - "typescript": "^4.9.4" + "typescript": "^4.9.4", + "uuid": "^8.3.2" }, "devDependencies": { "@types/dotenv": "^8.2.0", @@ -31,6 +32,7 @@ "@types/node": "^18.11.18", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", + "@types/uuid": "^8.3.4", "eslint": "^8.31.0", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^17.0.0", diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index eca5dbbb5..43949201b 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -9,7 +9,7 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL || 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v2.2023-02-07', + EXPECTED: 'v3.2023-04-26', CURRENT: '', }, } @@ -28,6 +28,12 @@ const database = { 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 = { FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB', FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null, @@ -51,6 +57,7 @@ const CONFIG = { ...constants, ...server, ...database, + ...community, ...federation, } diff --git a/dht-node/src/dht_node/index.test.ts b/dht-node/src/dht_node/index.test.ts index e76e6ac9f..ec172c4f8 100644 --- a/dht-node/src/dht_node/index.test.ts +++ b/dht-node/src/dht_node/index.test.ts @@ -5,8 +5,10 @@ import { startDHT } from './index' import DHT from '@hyperswarm/dht' import CONFIG from '@/config' import { logger } from '@test/testSetup' +import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { testEnvironment, cleanDB } from '@test/helpers' +import { validate as validateUUID, version as versionUUID } from 'uuid' CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f' @@ -114,6 +116,9 @@ describe('federation', () => { const hashSpy = jest.spyOn(DHT, 'hash') const keyPairSpy = jest.spyOn(DHT, 'keyPair') 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() jest.clearAllMocks() await cleanDB() @@ -132,6 +137,64 @@ describe('federation', () => { 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', () => { it('creates a server', () => { expect(nodeCreateServerMock).toBeCalled() @@ -780,21 +843,21 @@ describe('federation', () => { socketEventMocks.open() }) - it.skip('calls socket write with own api versions', () => { + it('calls socket write with own api versions', () => { expect(socketWriteMock).toBeCalledWith( Buffer.from( JSON.stringify([ { api: '1_0', - url: 'http://localhost/api/', + url: 'https://test.gradido.net/api/', }, { api: '1_1', - url: 'http://localhost/api/', + url: 'https://test.gradido.net/api/', }, { 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/', + }, + ]) + }) + }) + }) }) }) diff --git a/dht-node/src/dht_node/index.ts b/dht-node/src/dht_node/index.ts index 0db7a28c2..36291904a 100644 --- a/dht-node/src/dht_node/index.ts +++ b/dht-node/src/dht_node/index.ts @@ -4,10 +4,15 @@ import DHT from '@hyperswarm/dht' import { logger } from '@/server/logger' import CONFIG from '@/config' 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 getSeed = (): Buffer | null => - CONFIG.FEDERATION_DHT_SEED ? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED) : null +const getSeed = (): Buffer | null => { + return CONFIG.FEDERATION_DHT_SEED + ? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED) + : null +} const POLLTIME = 20000 const SUCCESSTIME = 120000 @@ -19,7 +24,7 @@ enum ApiVersionType { V1_1 = '1_1', V2_0 = '2_0', } -type CommunityApi = { +export type CommunityApi = { api: string url: string } @@ -28,10 +33,12 @@ export const startDHT = async (topic: string): Promise => { try { const TOPIC = DHT.hash(Buffer.from(topic)) 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')}`) + await writeHomeCommunityEntry(pubKeyString) - const ownApiVersions = await writeFederatedHomeCommunityEnries(keyPair.publicKey) + const ownApiVersions = await writeFederatedHomeCommunityEntries(pubKeyString) logger.info(`ApiList: ${JSON.stringify(ownApiVersions)}`) const node = new DHT({ keyPair }) @@ -138,7 +145,7 @@ export const startDHT = async (topic: string): Promise => { data.peers.forEach((peer: any) => { const pubKey = peer.publicKey.toString('hex') if ( - pubKey !== keyPair.publicKey.toString('hex') && + pubKey !== pubKeyString && !successfulRequests.includes(pubKey) && !errorfulRequests.includes(pubKey) && !collectedPubKeys.includes(pubKey) @@ -179,7 +186,7 @@ export const startDHT = async (topic: string): Promise => { } } -async function writeFederatedHomeCommunityEnries(pubKey: any): Promise { +async function writeFederatedHomeCommunityEntries(pubKey: string): Promise { const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) { const comApi: CommunityApi = { api: apiEnum, @@ -189,21 +196,68 @@ async function writeFederatedHomeCommunityEnries(pubKey: any): Promise { + 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 => { + let uuid: string + let countIds: number + do { + uuid = uuidv4() + countIds = await DbCommunity.count({ where: { communityUuid: uuid } }) + if (countIds > 0) { + logger.info('CommunityUuid creation conflict...') + } + } while (countIds > 0) + return uuid +} diff --git a/dht-node/src/index.ts b/dht-node/src/index.ts index 2315c77df..d5e5f700b 100644 --- a/dht-node/src/index.ts +++ b/dht-node/src/index.ts @@ -21,9 +21,8 @@ async function main() { logger.fatal('Fatal: Database Version incorrect') throw new Error('Fatal: Database Version incorrect') } - - // eslint-disable-next-line no-console - console.log( + logger.debug(`dhtseed set by CONFIG.FEDERATION_DHT_SEED=${CONFIG.FEDERATION_DHT_SEED}`) + logger.info( `starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${ CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...' }`, diff --git a/dht-node/test/helpers.ts b/dht-node/test/helpers.ts index aa7f94964..c5d6ce82b 100644 --- a/dht-node/test/helpers.ts +++ b/dht-node/test/helpers.ts @@ -22,8 +22,8 @@ const context = { export const cleanDB = async () => { // this only works as long we do not have foreign key constraints - for (let i = 0; i < entities.length; i++) { - await resetEntity(entities[i]) + for (const entity of entities) { + await resetEntity(entity) } } diff --git a/dht-node/yarn.lock b/dht-node/yarn.lock index 5832ecd8b..85c4d35fa 100644 --- a/dht-node/yarn.lock +++ b/dht-node/yarn.lock @@ -769,6 +769,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" 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@*": version "21.0.0" 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" 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: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"