refactored, not final yet

This commit is contained in:
einhorn_b 2023-10-24 19:48:01 +02:00
parent 2ff7adef52
commit 8ccc52ae0a
16 changed files with 214 additions and 161 deletions

View File

@ -14,6 +14,7 @@ module.exports = {
// 'plugin:import/typescript',
// 'plugin:security/recommended',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:dci-lint/recommended',
],
settings: {
'import/parsers': {
@ -36,6 +37,7 @@ module.exports = {
htmlWhitespaceSensitivity: 'ignore',
},
],
// 'dci-lint/literal-role-contracts': 'off'
// import
// 'import/export': 'error',
// 'import/no-deprecated': 'error',

View File

@ -53,6 +53,7 @@
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.4",
"eslint-plugin-dci-lint": "^0.3.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-n": "^15.7.0",

View File

@ -0,0 +1,44 @@
import { KeyManager } from '@/controller/KeyManager'
import { KeyPair } from '@/data/KeyPair'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { hardenDerivationIndex } from '@/utils/derivationHelper'
import { Account } from '@entity/Account'
import Decimal from 'decimal.js-light'
const GMW_ACCOUNT_DERIVATION_INDEX = 1
const AUF_ACCOUNT_DERIVATION_INDEX = 2
export class AccountFactory {
public static createAccount(
keyPair: KeyPair,
createdAt: Date,
derivationIndex: number,
type: AddressType,
): Account {
const account = Account.create()
account.derivationIndex = derivationIndex
account.derive2Pubkey = KeyManager.getInstance().derive([derivationIndex], keyPair).publicKey
account.type = type.valueOf()
account.createdAt = createdAt
account.balance = new Decimal(0)
return account
}
public static createGmwAccount(keyPair: KeyPair, createdAt: Date): Account {
return AccountFactory.createAccount(
keyPair,
createdAt,
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
AddressType.COMMUNITY_GMW,
)
}
public static createAufAccount(keyPair: KeyPair, createdAt: Date): Account {
return AccountFactory.createAccount(
keyPair,
createdAt,
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
AddressType.COMMUNITY_AUF,
)
}
}

View File

@ -1,42 +0,0 @@
import { KeyManager } from '@/controller/KeyManager'
import { KeyPair } from '@/data/KeyPair'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { hardenDerivationIndex } from '@/utils/derivationHelper'
import { Account } from '@entity/Account'
import Decimal from 'decimal.js-light'
const GMW_ACCOUNT_DERIVATION_INDEX = 1
const AUF_ACCOUNT_DERIVATION_INDEX = 2
export const createAccount = (
keyPair: KeyPair,
createdAt: Date,
derivationIndex: number,
type: AddressType,
): Account => {
const account = Account.create()
account.derivationIndex = derivationIndex
account.derive2Pubkey = KeyManager.getInstance().derive([derivationIndex], keyPair).publicKey
account.type = type.valueOf()
account.createdAt = createdAt
account.balance = new Decimal(0)
return account
}
export const createGmwAccount = (keyPair: KeyPair, createdAt: Date): Account => {
return createAccount(
keyPair,
createdAt,
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
AddressType.COMMUNITY_GMW,
)
}
export const createAufAccount = (keyPair: KeyPair, createdAt: Date): Account => {
return createAccount(
keyPair,
createdAt,
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
AddressType.COMMUNITY_AUF,
)
}

View File

@ -1,31 +0,0 @@
import { KeyManager } from '@/controller/KeyManager'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
import { Community } from '@entity/Community'
import { createAufAccount, createGmwAccount } from './account.factory'
export const createCommunity = (communityDraft: CommunityDraft, topic?: string): Community => {
const communityEntity = Community.create()
communityEntity.iotaTopic = topic ?? iotaTopicFromCommunityUUID(communityDraft.uuid)
communityEntity.createdAt = new Date(communityDraft.createdAt)
communityEntity.foreign = communityDraft.foreign
return communityEntity
}
export const createHomeCommunity = (communityDraft: CommunityDraft, topic?: string): Community => {
// create community entity
const community = createCommunity(communityDraft, topic)
// generate key pair for signing transactions and deriving all keys for community
const keyPair = KeyManager.generateKeyPair()
community.rootPubkey = keyPair.publicKey
community.rootPrivkey = keyPair.privateKey
community.rootChaincode = keyPair.chainCode
// we should only have one home community per server
KeyManager.getInstance().setHomeCommunityKeyPair(keyPair)
// create auf account and gmw account
community.aufAccount = createAufAccount(keyPair, community.createdAt)
community.gmwAccount = createGmwAccount(keyPair, community.createdAt)
return community
}

View File

@ -11,7 +11,7 @@ import { CommunityArg } from '@arg/CommunityArg'
import { LogError } from '@/server/LogError'
import { logger } from '@/server/logger'
import { CommunityRepository } from '@/data/Community.repository'
import { addCommunity } from '@/interactions/backendToDb/community/community.context'
import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context'
@Resolver()
export class CommunityResolver {
@ -54,6 +54,20 @@ export class CommunityResolver {
new TransactionError(TransactionErrorType.ALREADY_EXIST, 'community already exist!'),
)
}
return await addCommunity(communityDraft, topic)
// prepare context for interaction
// shouldn't throw at all
// TODO: write tests to make sure that it doesn't throw
const addCommunityContext = new AddCommunityContext(communityDraft, topic)
try {
// actually run interaction, create community, accounts for foreign community and transactionRecipe
await addCommunityContext.run()
return new TransactionResult()
} catch (error) {
if (error instanceof TransactionError) {
return new TransactionResult(error)
} else {
throw error
}
}
}
}

View File

@ -0,0 +1,30 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { ForeignCommunityRole } from './ForeignCommunity.role'
import { HomeCommunityRole } from './HomeCommunity.role'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
import { CommunityRole } from './Community.role'
/**
* @DCI-Context
* Context for adding community to DB
* using roles to distinct between foreign and home communities
*/
export class AddCommunityContext {
private communityRole: CommunityRole
private iotaTopic: string
public constructor(private communityDraft: CommunityDraft, iotaTopic?: string) {
if (!iotaTopic) {
this.iotaTopic = iotaTopicFromCommunityUUID(this.communityDraft.uuid)
} else {
this.iotaTopic = iotaTopic
}
this.communityRole = communityDraft.foreign
? new ForeignCommunityRole()
: new HomeCommunityRole()
}
public async run(): Promise<void> {
this.communityRole.create(this.communityDraft, this.iotaTopic)
await this.communityRole.store()
}
}

View File

@ -1,6 +1,27 @@
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { logger } from '@/server/logger'
import { Community } from '@entity/Community'
export abstract class CommunityRole {
abstract addCommunity(communityDraft: CommunityDraft, topic: string): Promise<Community>
protected self: Community
public constructor() {
this.self = Community.create()
}
public create(communityDraft: CommunityDraft, topic: string): void {
this.self.iotaTopic = topic
this.self.createdAt = new Date(communityDraft.createdAt)
this.self.foreign = communityDraft.foreign
}
public store(): Promise<Community> {
try {
return this.self.save()
} catch (error) {
logger.error('error saving new community into db: %s', error)
throw new TransactionError(TransactionErrorType.DB_ERROR, 'error saving community into db')
}
}
}

View File

@ -1,19 +1,4 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { CommunityRole } from './Community.role'
import { logger } from '@/server/logger'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { createCommunity } from '@/data/community.factory'
export class ForeignCommunityRole extends CommunityRole {
addCommunity(communityDraft: CommunityDraft, topic: string): Promise<Community> {
const community = createCommunity(communityDraft, topic)
try {
return community.save()
} catch (error) {
logger.error('error saving new foreign community into db: %s', error)
throw new TransactionError(TransactionErrorType.DB_ERROR, 'error saving community into db')
}
}
}
// same as base class
export class ForeignCommunityRole extends CommunityRole {}

View File

@ -1,28 +1,47 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { CommunityRole } from './Community.role'
import { getTransaction } from '@/client/GradidoNode'
import { timestampSecondsToDate } from '@/utils/typeConverter'
import { createHomeCommunity } from '@/data/community.factory'
import { createCommunityRootTransactionRecipe } from '../transaction/transaction.context'
import { QueryRunner } from 'typeorm'
import { Transaction } from '@entity/Transaction'
import { KeyManager } from '@/controller/KeyManager'
import { AccountFactory } from '@/data/Account.factory'
import { CreateTransactionRecipeContext } from '../transaction/CreateTransationRecipe.context'
export class HomeCommunityRole extends CommunityRole {
private transactionRecipe: Transaction
public create(communityDraft: CommunityDraft, topic: string): void {
super.create(communityDraft, topic)
// generate key pair for signing transactions and deriving all keys for community
const keyPair = KeyManager.generateKeyPair()
this.self.rootPubkey = keyPair.publicKey
this.self.rootPrivkey = keyPair.privateKey
this.self.rootChaincode = keyPair.chainCode
// we should only have one home community per server
KeyManager.getInstance().setHomeCommunityKeyPair(keyPair)
// create auf account and gmw account
this.self.aufAccount = AccountFactory.createAufAccount(keyPair, this.self.createdAt)
this.self.gmwAccount = AccountFactory.createGmwAccount(keyPair, this.self.createdAt)
const transactionRecipeContext = new CreateTransactionRecipeContext(communityDraft)
transactionRecipeContext.setCommunity(this.self)
transactionRecipeContext.run()
this.transactionRecipe = transactionRecipeContext.getTransactionRecipe()
}
public store(): Promise<Community> {
}
public async addCommunity(communityDraft: CommunityDraft, topic: string): Promise<Community> {
const community = createHomeCommunity(communityDraft, topic)
// check if a CommunityRoot Transaction exist already on iota blockchain
const existingCommunityRootTransaction = await getTransaction(1, community.iotaTopic)
if (existingCommunityRootTransaction) {
community.confirmedAt = timestampSecondsToDate(existingCommunityRootTransaction.confirmedAt)
return community.save()
} else {
createCommunityRootTransactionRecipe(communityDraft, community).storeAsTransaction(
async (queryRunner: QueryRunner): Promise<void> => {
await queryRunner.manager.save(community)
},
)
}
createCommunityRootTransactionRecipe(communityDraft, community).storeAsTransaction(
async (queryRunner: QueryRunner): Promise<void> => {
await queryRunner.manager.save(community)
},
)
return community.save()
}
}

View File

@ -1,30 +0,0 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionResult } from '@/graphql/model/TransactionResult'
import { ForeignCommunityRole } from './ForeignCommunity.role'
import { HomeCommunityRole } from './HomeCommunity.role'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionsManager } from '@/controller/TransactionsManager'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
export const addCommunity = async (
communityDraft: CommunityDraft,
iotaTopic?: string,
): Promise<TransactionResult> => {
const communityRole = communityDraft.foreign
? new ForeignCommunityRole()
: new HomeCommunityRole()
try {
if (!iotaTopic) {
iotaTopic = iotaTopicFromCommunityUUID(communityDraft.uuid)
}
await communityRole.addCommunity(communityDraft, iotaTopic)
await TransactionsManager.getInstance().addTopic(iotaTopic)
return new TransactionResult()
} catch (error) {
if (error instanceof TransactionError) {
return new TransactionResult(error)
} else {
throw error
}
}
}

View File

@ -6,7 +6,7 @@ import { KeyPair } from '@/data/KeyPair'
import { sign } from '@/utils/cryptoHelper'
export class CommunityRootTransactionRole extends TransactionRecipeRole {
public createFromCommunityDraft(
public createFromCommunityRoot(
communityDraft: CommunityDraft,
community: Community,
): CommunityRootTransactionRole {

View File

@ -0,0 +1,51 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { TransactionRecipeRole } from './TransactionRecipe.role'
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { Transaction } from '@entity/Transaction'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
/**
* @DCI-Context
* Context for create and add Transaction Recipe to DB
*/
export class CreateTransactionRecipeContext {
private transactionRecipeRole: TransactionRecipeRole
private community?: Community
public constructor(private draft: CommunityDraft | TransactionDraft) {
if (draft instanceof CommunityDraft) {
this.transactionRecipeRole = new CommunityRootTransactionRole()
} else if (draft instanceof TransactionDraft) {
this.transactionRecipeRole = new TransactionRecipeRole()
}
}
public setCommunity(community: Community) {
this.community = community
}
public getCommunity() {
return this.community
}
public getTransactionRecipe(): Transaction {
return this.transactionRecipeRole.getTransaction()
}
public run(): void {
if (this.draft instanceof TransactionDraft) {
this.transactionRecipeRole = new TransactionRecipeRole().create(this.draft)
} else if (this.draft instanceof CommunityDraft) {
if (!this.community) {
throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'community was not set')
}
this.transactionRecipeRole = new CommunityRootTransactionRole().createFromCommunityRoot(
this.draft,
this.community,
)
}
}
}

View File

@ -14,10 +14,14 @@ export class TransactionRecipeRole {
this.transactionBuilder = new TransactionBuilder()
}
public createFromTransactionDraft(transactionDraft: TransactionDraft): TransactionRecipeRole {
public create(transactionDraft: TransactionDraft): TransactionRecipeRole {
return this
}
public getTransaction(): Transaction {
return this.transactionBuilder.getTransaction()
}
public async storeAsTransaction(
transactionFunction: (queryRunner: QueryRunner) => Promise<void>,
): Promise<TransactionResult> {

View File

@ -1,20 +0,0 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { TransactionRecipeRole } from './TransactionRecipe.role'
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
export const createCommunityRootTransactionRecipe = (
communityDraft: CommunityDraft,
community: Community,
): TransactionRecipeRole => {
const communityRootTransactionRole = new CommunityRootTransactionRole()
return communityRootTransactionRole.createFromCommunityDraft(communityDraft, community)
}
export const createTransactionRecipe = (
transactionDraft: TransactionDraft,
): TransactionRecipeRole => {
const transactionRecipeRole = new TransactionRecipeRole()
return transactionRecipeRole.createFromTransactionDraft(transactionDraft)
}

View File

@ -2586,6 +2586,11 @@ eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0:
dependencies:
debug "^3.2.7"
eslint-plugin-dci-lint@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-dci-lint/-/eslint-plugin-dci-lint-0.3.0.tgz#dcd73c50505b589b415017cdb72716f98e9495c3"
integrity sha512-BhgrwJ5k3eMN41NwCZ/tYQGDTMOrHXpH8XOfRZrGtPqmlnOZCVGWow+KyZMz0/wOFVpXx/q9B0y7R7qtU7lnqg==
eslint-plugin-es@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz#f0822f0c18a535a97c3e714e89f88586a7641ec9"