From b081bddb7d41badc8f0b4b9f45e549be0f2a8f1b Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Tue, 13 Feb 2024 11:48:14 -0500 Subject: [PATCH 01/17] refactor tbc --- libs/model/src/index.ts | 3 +- .../commonProtocol/contractHelpers.ts | 6 +- .../commonProtocol/newNamespaceValidator.ts | 6 +- libs/model/src/services/index.ts | 1 + .../providers/cacheBalances.ts | 92 +++++ .../providers/getCosmosBalances.ts | 88 +++++ .../providers/getEvmBalances.ts | 81 ++++ .../tokenBalanceCache/providers/index.ts | 2 + .../tokenBalanceCache/tokenBalanceCache.ts | 351 +++--------------- packages/commonwealth/main.ts | 9 +- .../scripts/refresh-all-memberships.ts | 11 +- packages/commonwealth/server-test.ts | 9 +- .../controllers/server_comments_controller.ts | 2 - .../create_comment_reaction.ts | 19 +- .../server_communities_controller.ts | 12 +- .../update_community.ts | 5 +- .../controllers/server_groups_controller.ts | 8 +- .../refresh_community_memberships.ts | 3 +- .../refresh_membership.ts | 1 - .../controllers/server_polls_controller.ts | 4 +- .../server_polls_methods/update_poll_vote.ts | 1 - .../controllers/server_threads_controller.ts | 2 - .../server_threads_methods/create_thread.ts | 1 - .../create_thread_comment.ts | 1 - .../create_thread_reaction.ts | 19 +- .../create_community_stakes_handler.ts | 4 +- .../commonwealth/server/routing/router.ts | 21 +- .../refreshMembershipsForAddress.ts | 3 +- .../validateTopicGroupsMembership.ts | 4 - .../cosmos/tokenBalanceFetching.spec.ts | 115 +++--- .../devnet/evm/tokenBalanceFetching.spec.ts | 120 +++--- .../test/integration/api/chainNodes.spec.ts | 2 +- .../integration/api/communityStake.spec.ts | 6 +- .../test/integration/api/createChain.spec.ts | 4 +- .../api/getRelatedCommunities.spec.ts | 4 +- .../test/integration/api/updateChain.spec.ts | 13 +- .../server_comments_controller.spec.ts | 28 +- .../server_groups_controller.spec.ts | 7 +- .../server_reactions_controller.spec.ts | 6 +- .../server_threads_controller.spec.ts | 42 +-- ...r_threads_controller.update_thread.spec.ts | 7 +- 41 files changed, 509 insertions(+), 614 deletions(-) create mode 100644 libs/model/src/services/tokenBalanceCache/providers/cacheBalances.ts create mode 100644 libs/model/src/services/tokenBalanceCache/providers/getCosmosBalances.ts create mode 100644 libs/model/src/services/tokenBalanceCache/providers/getEvmBalances.ts create mode 100644 libs/model/src/services/tokenBalanceCache/providers/index.ts diff --git a/libs/model/src/index.ts b/libs/model/src/index.ts index 497ab7b5f4b..d1bbf3e3a3b 100644 --- a/libs/model/src/index.ts +++ b/libs/model/src/index.ts @@ -6,8 +6,7 @@ export * as Thread from './thread'; export * as User from './user'; // Core Services -export * from './services/commonProtocol'; -export * from './services/tokenBalanceCache'; +export * from './services'; // Test Service export * as tester from './test'; diff --git a/libs/model/src/services/commonProtocol/contractHelpers.ts b/libs/model/src/services/commonProtocol/contractHelpers.ts index 11a3dde68b9..6647d611c30 100644 --- a/libs/model/src/services/commonProtocol/contractHelpers.ts +++ b/libs/model/src/services/commonProtocol/contractHelpers.ts @@ -6,7 +6,7 @@ import { import Web3 from 'web3'; import { AbiItem } from 'web3-utils'; import { DB } from '../../models'; -import { TokenBalanceCache } from '../tokenBalanceCache'; +import { getBalances } from '../tokenBalanceCache'; export const getNamespace = async ( web3: Web3, @@ -45,7 +45,6 @@ export const getNamespace = async ( /** * gets the balance of an id for an address on a namespace - * @param tbc TokenBalanceCache instance * @param namespace namespace name * @param tokenId ERC1155 id(ie 0 for admin token, default 2 for CommunityStake) * @param chain chainNode to use(must be chain with deployed protocol) @@ -54,7 +53,6 @@ export const getNamespace = async ( * @returns balance in wei */ export const getNamespaceBalance = async ( - tbc: TokenBalanceCache, namespace: string, tokenId: number, chain: commonProtocol.ValidChains, @@ -78,7 +76,7 @@ export const getNamespaceBalance = async ( if (activeNamespace === '0x0000000000000000000000000000000000000000') { throw new AppError('Namespace not found for this name'); } - const balance = await tbc.getBalances({ + const balance = await getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [address], sourceOptions: { diff --git a/libs/model/src/services/commonProtocol/newNamespaceValidator.ts b/libs/model/src/services/commonProtocol/newNamespaceValidator.ts index acea2f02c5e..50a0e2a2707 100644 --- a/libs/model/src/services/commonProtocol/newNamespaceValidator.ts +++ b/libs/model/src/services/commonProtocol/newNamespaceValidator.ts @@ -5,7 +5,7 @@ import { } from '@hicommonwealth/core'; import Web3 from 'web3'; import { CommunityAttributes } from '../../models'; -import { TokenBalanceCache } from '../tokenBalanceCache'; +import { getBalances } from '../tokenBalanceCache'; import { getNamespace } from './contractHelpers'; /** @@ -15,7 +15,6 @@ import { getNamespace } from './contractHelpers'; * 3. correct contract address * 4. If user is the admin of namespace on-chain * @param model - * @param tbc * @param namespace The namespace name * @param txHash transaction hash of creation tx * @param address user's address @@ -23,7 +22,6 @@ import { getNamespace } from './contractHelpers'; * @returns an AppError if any validations fail, else passses */ export const validateNamespace = async ( - tbc: TokenBalanceCache, namespace: string, txHash: string, address: string, @@ -73,7 +71,7 @@ export const validateNamespace = async ( } // Validate User as admin - const balance = await tbc.getBalances({ + const balance = await getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [address], sourceOptions: { diff --git a/libs/model/src/services/index.ts b/libs/model/src/services/index.ts index 705caab7f75..3cafcc500df 100644 --- a/libs/model/src/services/index.ts +++ b/libs/model/src/services/index.ts @@ -1,2 +1,3 @@ export * as commonProtocol from './commonProtocol'; export * as tokenBalanceCache from './tokenBalanceCache'; +export * from './tokenBalanceCache/types'; diff --git a/libs/model/src/services/tokenBalanceCache/providers/cacheBalances.ts b/libs/model/src/services/tokenBalanceCache/providers/cacheBalances.ts new file mode 100644 index 00000000000..97552bc8c32 --- /dev/null +++ b/libs/model/src/services/tokenBalanceCache/providers/cacheBalances.ts @@ -0,0 +1,92 @@ +import { + BalanceSourceType, + CacheNamespaces, + cache, +} from '@hicommonwealth/core'; +import { Balances, GetBalancesOptions } from '../types'; + +const balanceTTL = process.env.TBC_BALANCE_TTL_SECONDS + ? parseInt(process.env.TBC_BALANCE_TTL_SECONDS, 10) + : 300; + +/** + * This function retrieves cached balances and modifies (in-place) the given addresses array + * to remove addresses whose balance was cached. This means that after executing this function, + * the addresses array only contains addresses whose balance was not cached. + */ +export async function getCachedBalances( + options: GetBalancesOptions, + addresses: string[], +): Promise { + const balances: Balances = {}; + if (!options.cacheRefresh) { + const result = await cache().getKeys( + CacheNamespaces.Token_Balance, + addresses.map((address) => buildCacheKey(options, address)), + ); + if (result !== false) { + for (const [key, balance] of Object.entries(result)) { + const address = getAddressFromCacheKey(key); + balances[address] = balance as string; + const addressIndex = addresses.indexOf(address); + addresses[addressIndex] = addresses[addresses.length - 1]; + addresses.pop(); + } + } + } + return balances; +} + +export async function cacheBalances( + options: GetBalancesOptions, + balances: Balances, + ttl?: number, +) { + if (Object.keys(balances).length > 0) { + await cache().setKeys( + CacheNamespaces.Token_Balance, + Object.keys(balances).reduce((result, address) => { + const transformedKey = buildCacheKey(options, address); + result[transformedKey] = balances[address]; + return result; + }, {} as Balances), + ttl ?? balanceTTL, + false, + ); + } +} + +/** + * This function builds the cache key for a specific balance on any chain, contract, or token. + * WARNING: address MUST always be the last value in the key so that we can easily derive + * a wallet address from the cache key. + */ +function buildCacheKey(options: GetBalancesOptions, address: string): string { + switch (options.balanceSourceType) { + case BalanceSourceType.ETHNative: + return `${options.sourceOptions.evmChainId}_${address}`; + case BalanceSourceType.ERC20: + case BalanceSourceType.ERC721: + return ( + `${options.sourceOptions.evmChainId}_` + + `${options.sourceOptions.contractAddress}_${address}` + ); + case BalanceSourceType.ERC1155: + return ( + `${options.sourceOptions.evmChainId}_` + + `${options.sourceOptions.contractAddress}_` + + `${options.sourceOptions.tokenId}_${address}` + ); + case BalanceSourceType.CosmosNative: + return `${options.sourceOptions.cosmosChainId}_${address}`; + case BalanceSourceType.CW721: + return ( + `${options.sourceOptions.cosmosChainId}_` + + `${options.sourceOptions.contractAddress}_${address}` + ); + } +} + +function getAddressFromCacheKey(key: string): string { + return key.substring(key.lastIndexOf('_') + 1); +} diff --git a/libs/model/src/services/tokenBalanceCache/providers/getCosmosBalances.ts b/libs/model/src/services/tokenBalanceCache/providers/getCosmosBalances.ts new file mode 100644 index 00000000000..b8dde056e22 --- /dev/null +++ b/libs/model/src/services/tokenBalanceCache/providers/getCosmosBalances.ts @@ -0,0 +1,88 @@ +import { fromBech32, toBech32 } from '@cosmjs/encoding'; +import { BalanceSourceType, logger } from '@hicommonwealth/core'; +import { models } from '../../../database'; +import { Balances, GetCosmosBalancesOptions } from '../types'; +import { cacheBalances, getCachedBalances } from './cacheBalances'; +import { __getCosmosNativeBalances } from './get_cosmos_balances'; +import { __getCw721Balances } from './get_cw721_balances'; + +const log = logger().getLogger(__filename); + +export async function getCosmosBalances( + options: GetCosmosBalancesOptions, + ttl?: number, +) { + const chainNode = await models.ChainNode.scope('withPrivateData').findOne({ + where: { + cosmos_chain_id: options.sourceOptions.cosmosChainId, + }, + }); + + if (!chainNode) { + const msg = `ChainNode with cosmos_chain_id ${options.sourceOptions.cosmosChainId} does not exist`; + log.error(msg); + return {}; + } + + // maps an encoded address to a decoded address to avoid having to decode + // all addresses twice before returning + const addressMap: { [encodedAddress: string]: string } = {}; + for (const address of options.addresses) { + try { + const { data } = fromBech32(address); + const encodedAddress = toBech32(chainNode.bech32!, data); + addressMap[encodedAddress] = address; + } catch (e) { + if (address != '0xdiscordbot') { + log.error( + `Skipping address: ${address}`, + e instanceof Error ? e : undefined, + ); + } + } + } + + const validatedAddresses = Object.keys(addressMap); + if (validatedAddresses.length === 0) return {}; + + const cachedBalances = await getCachedBalances(options, validatedAddresses); + + let freshBalances = {}; + switch (options.balanceSourceType) { + case BalanceSourceType.CosmosNative: + freshBalances = await __getCosmosNativeBalances({ + chainNode, + addresses: validatedAddresses, + batchSize: options.batchSize, + }); + break; + case BalanceSourceType.CW721: + freshBalances = await __getCw721Balances({ + chainNode, + addresses: validatedAddresses, + contractAddress: options.sourceOptions.contractAddress, + batchSize: options.batchSize, + }); + break; + } + + await cacheBalances(options, freshBalances, ttl); + + // this function facilitates reverting addresses to the format that was requested + // e.g. you could request osmosis balance and give a juno address -> + // to fetch the osmosis balance we convert juno address to osmosis address + // and this function undoes that change + const transformAddresses = (balances: Balances): Balances => { + const result: Balances = {}; + for (const [address, balance] of Object.entries(balances)) { + result[addressMap[address]] = balance; + } + return result; + }; + + // map to decoded addresses rather than the generated encoded addresses + const transformedFreshBalances = transformAddresses(freshBalances); + const transformedCachedBalances = transformAddresses(cachedBalances); + + return { ...transformedFreshBalances, ...transformedCachedBalances }; +} diff --git a/libs/model/src/services/tokenBalanceCache/providers/getEvmBalances.ts b/libs/model/src/services/tokenBalanceCache/providers/getEvmBalances.ts new file mode 100644 index 00000000000..11af75bc99c --- /dev/null +++ b/libs/model/src/services/tokenBalanceCache/providers/getEvmBalances.ts @@ -0,0 +1,81 @@ +import { BalanceSourceType, logger } from '@hicommonwealth/core'; +import Web3 from 'web3'; +import { models } from '../../../database'; +import { Balances, GetEvmBalancesOptions } from '../types'; +import { cacheBalances, getCachedBalances } from './cacheBalances'; +import { __getErc1155Balances } from './get_erc1155_balances'; +import { __getErc20Balances } from './get_erc20_balances'; +import { __getErc721Balances } from './get_erc721_balances'; +import { __getEthBalances } from './get_eth_balances'; + +const log = logger().getLogger(__filename); + +export async function getEvmBalances( + options: GetEvmBalancesOptions, + ttl?: number, +) { + const validatedAddresses: string[] = []; + for (const address of options.addresses) { + if (Web3.utils.isAddress(address)) { + validatedAddresses.push(address); + } else { + log.info(`Skipping non-address ${address}`); + } + } + + if (validatedAddresses.length === 0) return {}; + + const cachedBalances = await getCachedBalances(options, validatedAddresses); + + const chainNode = await models.ChainNode.scope('withPrivateData').findOne({ + where: { + eth_chain_id: options.sourceOptions.evmChainId, + }, + }); + + if (!chainNode) { + const msg = `ChainNode with eth_chain_id ${options.sourceOptions.evmChainId} does not exist`; + log.error(msg); + return {}; + } + + let freshBalances: Balances = {}; + switch (options.balanceSourceType) { + case BalanceSourceType.ETHNative: + freshBalances = await __getEthBalances({ + chainNode, + addresses: validatedAddresses, + batchSize: options.batchSize, + }); + break; + case BalanceSourceType.ERC20: + freshBalances = await __getErc20Balances({ + chainNode, + addresses: validatedAddresses, + contractAddress: options.sourceOptions.contractAddress, + batchSize: options.batchSize, + }); + break; + case BalanceSourceType.ERC721: + freshBalances = await __getErc721Balances({ + chainNode, + addresses: validatedAddresses, + contractAddress: options.sourceOptions.contractAddress, + batchSize: options.batchSize, + }); + break; + case BalanceSourceType.ERC1155: + freshBalances = await __getErc1155Balances({ + chainNode, + addresses: validatedAddresses, + contractAddress: options.sourceOptions.contractAddress, + tokenId: options.sourceOptions.tokenId, + batchSize: options.batchSize, + }); + break; + } + + await cacheBalances(options, freshBalances, ttl); + + return { ...freshBalances, ...cachedBalances }; +} diff --git a/libs/model/src/services/tokenBalanceCache/providers/index.ts b/libs/model/src/services/tokenBalanceCache/providers/index.ts new file mode 100644 index 00000000000..c4316aaf58c --- /dev/null +++ b/libs/model/src/services/tokenBalanceCache/providers/index.ts @@ -0,0 +1,2 @@ +export * from './getCosmosBalances'; +export * from './getEvmBalances'; diff --git a/libs/model/src/services/tokenBalanceCache/tokenBalanceCache.ts b/libs/model/src/services/tokenBalanceCache/tokenBalanceCache.ts index c724f2819c7..f02b20abec8 100644 --- a/libs/model/src/services/tokenBalanceCache/tokenBalanceCache.ts +++ b/libs/model/src/services/tokenBalanceCache/tokenBalanceCache.ts @@ -1,20 +1,5 @@ -import { fromBech32, toBech32 } from '@cosmjs/encoding'; -import { - BalanceSourceType, - CacheNamespaces, - ILogger, - cache, - logger, - stats, -} from '@hicommonwealth/core'; -import Web3 from 'web3'; -import { DB } from '../../models'; -import { __getCosmosNativeBalances } from './providers/get_cosmos_balances'; -import { __getCw721Balances } from './providers/get_cw721_balances'; -import { __getErc1155Balances } from './providers/get_erc1155_balances'; -import { __getErc20Balances } from './providers/get_erc20_balances'; -import { __getErc721Balances } from './providers/get_erc721_balances'; -import { __getEthBalances } from './providers/get_eth_balances'; +import { BalanceSourceType, logger, stats } from '@hicommonwealth/core'; +import { getCosmosBalances, getEvmBalances } from './providers'; import { Balances, GetBalancesOptions, @@ -23,293 +8,59 @@ import { GetEvmBalancesOptions, } from './types'; -export class TokenBalanceCache { - private _log: ILogger; - - constructor(public models: DB, public balanceTTL = 300) { - this._log = logger().getLogger(__filename); - } - - /** - * This is the main function through which all balances should be fetched. - * This function supports all balance sources and is fully compatible with Redis caching. - */ - public async getBalances(options: GetBalancesOptions): Promise { - if (options.addresses.length === 0) return {}; - - let balances: Balances = {}; - - try { - if ( - options.balanceSourceType === BalanceSourceType.CosmosNative || - options.balanceSourceType === BalanceSourceType.CW721 - ) { - balances = await this.getCosmosBalances(options); - } else { - balances = await this.getEvmBalances(options); - } - } catch (e) { - let chainId: string; - if ((options as GetEvmBalancesOptions).sourceOptions.evmChainId) { - chainId = `evm chain id ${ - (options as GetEvmBalancesOptions).sourceOptions.evmChainId - }`; - } else { - chainId = `cosmos chain id ${ - (options as GetCosmosBalancesOptions).sourceOptions.cosmosChainId - }`; - } - - let contractAddress: string = ''; - if ((options as GetErcBalanceOptions).sourceOptions.contractAddress) { - contractAddress = ` for contract address ${ - (options as GetErcBalanceOptions).sourceOptions.contractAddress - }`; - } - const msg = - `Failed to fetch balance(s) for ${options.addresses.length}` + - ` address(es) on ${chainId}${contractAddress}`; - this._log.error(msg, e instanceof Error ? e : undefined); +const log = logger().getLogger(__filename); + +/** + * This is the main function through which all balances should be fetched. + * This function supports all balance sources and is fully compatible with Redis caching. + */ +export async function getBalances( + options: GetBalancesOptions, + ttl?: number, +): Promise { + if (options.addresses.length === 0) return {}; + + let balances: Balances = {}; + + try { + if ( + options.balanceSourceType === BalanceSourceType.CosmosNative || + options.balanceSourceType === BalanceSourceType.CW721 + ) { + balances = await getCosmosBalances(options, ttl); + } else { + balances = await getEvmBalances(options, ttl); } - - stats().incrementBy( - 'tbc.successful.balance.fetch', - Object.keys(balances).length, - { - balance_source_type: options.balanceSourceType, - }, - ); - return balances; - } - - private async getCosmosBalances(options: GetCosmosBalancesOptions) { - const chainNode = await this.models.ChainNode.scope( - 'withPrivateData', - ).findOne({ - where: { - cosmos_chain_id: options.sourceOptions.cosmosChainId, - }, - }); - - if (!chainNode) { - const msg = `ChainNode with cosmos_chain_id ${options.sourceOptions.cosmosChainId} does not exist`; - this._log.error(msg); - return {}; + } catch (e) { + let chainId: string; + if ((options as GetEvmBalancesOptions).sourceOptions.evmChainId) { + chainId = `evm chain id ${ + (options as GetEvmBalancesOptions).sourceOptions.evmChainId + }`; + } else { + chainId = `cosmos chain id ${ + (options as GetCosmosBalancesOptions).sourceOptions.cosmosChainId + }`; } - // maps an encoded address to a decoded address to avoid having to decode - // all addresses twice before returning - const addressMap: { [encodedAddress: string]: string } = {}; - for (const address of options.addresses) { - try { - const { data } = fromBech32(address); - const encodedAddress = toBech32(chainNode.bech32!, data); - addressMap[encodedAddress] = address; - } catch (e) { - if (address != '0xdiscordbot') { - this._log.error( - `Skipping address: ${address}`, - e instanceof Error ? e : undefined, - ); - } - } + let contractAddress: string = ''; + if ((options as GetErcBalanceOptions).sourceOptions.contractAddress) { + contractAddress = ` for contract address ${ + (options as GetErcBalanceOptions).sourceOptions.contractAddress + }`; } - - const validatedAddresses = Object.keys(addressMap); - if (validatedAddresses.length === 0) return {}; - - const cachedBalances = await this.getCachedBalances( - options, - validatedAddresses, - ); - - let freshBalances = {}; - switch (options.balanceSourceType) { - case BalanceSourceType.CosmosNative: - freshBalances = await __getCosmosNativeBalances.call(this, { - chainNode, - addresses: validatedAddresses, - batchSize: options.batchSize, - }); - break; - case BalanceSourceType.CW721: - freshBalances = await __getCw721Balances.call(this, { - chainNode, - addresses: validatedAddresses, - contractAddress: options.sourceOptions.contractAddress, - batchSize: options.batchSize, - }); - break; - } - - await this.cacheBalances(options, freshBalances); - - // this function facilitates reverting addresses to the format that was requested - // e.g. you could request osmosis balance and give a juno address -> - // to fetch the osmosis balance we convert juno address to osmosis address - // and this function undoes that change - const transformAddresses = (balances: Balances): Balances => { - const result: Balances = {}; - for (const [address, balance] of Object.entries(balances)) { - result[addressMap[address]] = balance; - } - return result; - }; - - // map to decoded addresses rather than the generated encoded addresses - const transformedFreshBalances = transformAddresses(freshBalances); - const transformedCachedBalances = transformAddresses(cachedBalances); - - return { ...transformedFreshBalances, ...transformedCachedBalances }; + const msg = + `Failed to fetch balance(s) for ${options.addresses.length}` + + ` address(es) on ${chainId}${contractAddress}`; + log.error(msg, e instanceof Error ? e : undefined); } - private async getEvmBalances(options: GetEvmBalancesOptions) { - const validatedAddresses: string[] = []; - for (const address of options.addresses) { - if (Web3.utils.isAddress(address)) { - validatedAddresses.push(address); - } else { - this._log.info(`Skipping non-address ${address}`); - } - } - - if (validatedAddresses.length === 0) return {}; - - const cachedBalances = await this.getCachedBalances( - options, - validatedAddresses, - ); - - const chainNode = await this.models.ChainNode.scope( - 'withPrivateData', - ).findOne({ - where: { - eth_chain_id: options.sourceOptions.evmChainId, - }, - }); - - if (!chainNode) { - const msg = `ChainNode with eth_chain_id ${options.sourceOptions.evmChainId} does not exist`; - this._log.error(msg); - return {}; - } - - let freshBalances: Balances = {}; - switch (options.balanceSourceType) { - case BalanceSourceType.ETHNative: - freshBalances = await __getEthBalances.call(this, { - chainNode, - addresses: validatedAddresses, - batchSize: options.batchSize, - }); - break; - case BalanceSourceType.ERC20: - freshBalances = await __getErc20Balances.call(this, { - chainNode, - addresses: validatedAddresses, - contractAddress: options.sourceOptions.contractAddress, - batchSize: options.batchSize, - }); - break; - case BalanceSourceType.ERC721: - freshBalances = await __getErc721Balances.call(this, { - chainNode, - addresses: validatedAddresses, - contractAddress: options.sourceOptions.contractAddress, - batchSize: options.batchSize, - }); - break; - case BalanceSourceType.ERC1155: - freshBalances = await __getErc1155Balances.call(this, { - chainNode, - addresses: validatedAddresses, - contractAddress: options.sourceOptions.contractAddress, - tokenId: options.sourceOptions.tokenId, - batchSize: options.batchSize, - }); - break; - } - - await this.cacheBalances(options, freshBalances); - - return { ...freshBalances, ...cachedBalances }; - } - - /** - * This function retrieves cached balances and modifies (in-place) the given addresses array - * to remove addresses whose balance was cached. This means that after executing this function, - * the addresses array only contains addresses whose balance was not cached. - */ - private async getCachedBalances( - options: GetBalancesOptions, - addresses: string[], - ): Promise { - const balances: Balances = {}; - if (!options.cacheRefresh) { - const result = await cache().getKeys( - CacheNamespaces.Token_Balance, - addresses.map((address) => this.buildCacheKey(options, address)), - ); - if (result !== false) { - for (const [key, balance] of Object.entries(result)) { - const address = this.getAddressFromCacheKey(key); - balances[address] = balance as string; - const addressIndex = addresses.indexOf(address); - addresses[addressIndex] = addresses[addresses.length - 1]; - addresses.pop(); - } - } - } - return balances; - } - - private async cacheBalances(options: GetBalancesOptions, balances: Balances) { - if (Object.keys(balances).length > 0) { - await cache().setKeys( - CacheNamespaces.Token_Balance, - Object.keys(balances).reduce((result, address) => { - const transformedKey = this.buildCacheKey(options, address); - result[transformedKey] = balances[address]; - return result; - }, {} as Balances), - this.balanceTTL, - false, - ); - } - } - - /** - * This function builds the cache key for a specific balance on any chain, contract, or token. - * WARNING: address MUST always be the last value in the key so that we can easily derive - * a wallet address from the cache key. - */ - private buildCacheKey(options: GetBalancesOptions, address: string): string { - switch (options.balanceSourceType) { - case BalanceSourceType.ETHNative: - return `${options.sourceOptions.evmChainId}_${address}`; - case BalanceSourceType.ERC20: - case BalanceSourceType.ERC721: - return ( - `${options.sourceOptions.evmChainId}_` + - `${options.sourceOptions.contractAddress}_${address}` - ); - case BalanceSourceType.ERC1155: - return ( - `${options.sourceOptions.evmChainId}_` + - `${options.sourceOptions.contractAddress}_` + - `${options.sourceOptions.tokenId}_${address}` - ); - case BalanceSourceType.CosmosNative: - return `${options.sourceOptions.cosmosChainId}_${address}`; - case BalanceSourceType.CW721: - return ( - `${options.sourceOptions.cosmosChainId}_` + - `${options.sourceOptions.contractAddress}_${address}` - ); - } - } - - private getAddressFromCacheKey(key: string): string { - return key.substring(key.lastIndexOf('_') + 1); - } + stats().incrementBy( + 'tbc.successful.balance.fetch', + Object.keys(balances).length, + { + balance_source_type: options.balanceSourceType, + }, + ); + return balances; } diff --git a/packages/commonwealth/main.ts b/packages/commonwealth/main.ts index b6fe6830c0e..4819e56e5d1 100644 --- a/packages/commonwealth/main.ts +++ b/packages/commonwealth/main.ts @@ -7,7 +7,7 @@ import { setupErrorHandlers, } from '@hicommonwealth/adapters'; import { logger as _logger, cache } from '@hicommonwealth/core'; -import { TokenBalanceCache, models } from '@hicommonwealth/model'; +import { models } from '@hicommonwealth/model'; import bodyParser from 'body-parser'; import compression from 'compression'; import SessionSequelizeStore from 'connect-session-sequelize'; @@ -30,7 +30,6 @@ import { REDIS_URL, SERVER_URL, SESSION_SECRET, - TBC_BALANCE_TTL_SECONDS, } from './server/config'; import DatabaseValidationService from './server/middleware/databaseValidationService'; import setupPassport from './server/passport'; @@ -206,11 +205,6 @@ export async function main(app: express.Express) { ); } - const tokenBalanceCache = new TokenBalanceCache( - models, - TBC_BALANCE_TTL_SECONDS, - ); - const banCache = new BanCache(models); const globalActivityCache = new GlobalActivityCache(models); @@ -227,7 +221,6 @@ export async function main(app: express.Express) { app, models, viewCountCache, - tokenBalanceCache, banCache, globalActivityCache, dbValidationService, diff --git a/packages/commonwealth/scripts/refresh-all-memberships.ts b/packages/commonwealth/scripts/refresh-all-memberships.ts index be38d29a03b..8776e5ce3d7 100644 --- a/packages/commonwealth/scripts/refresh-all-memberships.ts +++ b/packages/commonwealth/scripts/refresh-all-memberships.ts @@ -1,6 +1,6 @@ import { RedisCache } from '@hicommonwealth/adapters'; import { cache } from '@hicommonwealth/core'; -import { TokenBalanceCache, models } from '@hicommonwealth/model'; +import { models } from '@hicommonwealth/model'; import * as dotenv from 'dotenv'; import { REDIS_URL } from '../server/config'; import { ServerCommunitiesController } from '../server/controllers/server_communities_controller'; @@ -16,19 +16,12 @@ async function main() { const banCache = new BanCache(models); - const tokenBalanceCache = new TokenBalanceCache(models); - const communitiesController = new ServerCommunitiesController( models, - tokenBalanceCache, banCache, ); - const groupsController = new ServerGroupsController( - models, - tokenBalanceCache, - banCache, - ); + const groupsController = new ServerGroupsController(models, banCache); const communitiesResult = await communitiesController.getCommunities({ hasGroups: true, diff --git a/packages/commonwealth/server-test.ts b/packages/commonwealth/server-test.ts index e48d10afc99..408557b23da 100644 --- a/packages/commonwealth/server-test.ts +++ b/packages/commonwealth/server-test.ts @@ -5,7 +5,7 @@ import { setupErrorHandlers, } from '@hicommonwealth/adapters'; import { cache, logger } from '@hicommonwealth/core'; -import { TokenBalanceCache, models } from '@hicommonwealth/model'; +import { models } from '@hicommonwealth/model'; import bodyParser from 'body-parser'; import SessionSequelizeStore from 'connect-session-sequelize'; import cookieParser from 'cookie-parser'; @@ -14,7 +14,7 @@ import session from 'express-session'; import http from 'http'; import passport from 'passport'; import favicon from 'serve-favicon'; -import { SESSION_SECRET, TBC_BALANCE_TTL_SECONDS } from './server/config'; +import { SESSION_SECRET } from './server/config'; import DatabaseValidationService from './server/middleware/databaseValidationService'; import setupPassport from './server/passport'; import setupAPI from './server/routing/router'; // performance note: this takes 15 seconds @@ -34,10 +34,6 @@ const app = express(); const SequelizeStore = SessionSequelizeStore(session.Store); // set cache TTL to 1 second to test invalidation const viewCountCache = new ViewCountCache(1, 10 * 60); -const tokenBalanceCache = new TokenBalanceCache( - models, - TBC_BALANCE_TTL_SECONDS, -); const databaseValidationService = new DatabaseValidationService(models); let server; @@ -116,7 +112,6 @@ setupAPI( app, models, viewCountCache, - tokenBalanceCache, banCache, globalActivityCache, databaseValidationService, diff --git a/packages/commonwealth/server/controllers/server_comments_controller.ts b/packages/commonwealth/server/controllers/server_comments_controller.ts index c567276ce5b..adea8827cc2 100644 --- a/packages/commonwealth/server/controllers/server_comments_controller.ts +++ b/packages/commonwealth/server/controllers/server_comments_controller.ts @@ -1,7 +1,6 @@ import { DB } from '@hicommonwealth/model'; import BanCache from '../util/banCheckCache'; -import { TokenBalanceCache } from '@hicommonwealth/model'; import GlobalActivityCache from '../util/globalActivityCache'; import { @@ -32,7 +31,6 @@ import { export class ServerCommentsController { constructor( public models: DB, - public tokenBalanceCache: TokenBalanceCache, public banCache: BanCache, public globalActivityCache?: GlobalActivityCache, ) {} diff --git a/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts b/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts index 5be4d4fb5d7..0754edffe77 100644 --- a/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts +++ b/packages/commonwealth/server/controllers/server_comments_methods/create_comment_reaction.ts @@ -8,7 +8,7 @@ import { AddressInstance, ReactionAttributes, UserInstance, - contractHelpers, + commonProtocol as commonProtocolService, } from '@hicommonwealth/model'; import { REACTION_WEIGHT_OVERRIDE } from 'server/config'; import { MixpanelCommunityInteractionEvent } from '../../../shared/analytics/types'; @@ -96,7 +96,6 @@ export async function __createCommentReaction( try { const { isValid } = await validateTopicGroupsMembership( this.models, - this.tokenBalanceCache, thread.topic_id, thread.community_id, address, @@ -126,14 +125,14 @@ export async function __createCommentReaction( if (!community) { throw new AppError(Errors.CommunityNotFound); } - const stakeBalance = await contractHelpers.getNamespaceBalance( - this.tokenBalanceCache, - community.namespace, - stake.stake_id, - commonProtocol.ValidChains.Sepolia, - address.address, - this.models, - ); + const stakeBalance = + await commonProtocolService.contractHelpers.getNamespaceBalance( + community.namespace, + stake.stake_id, + commonProtocol.ValidChains.Sepolia, + address.address, + this.models, + ); calculatedVotingWeight = commonProtocol.calculateVoteWeight( stakeBalance, voteWeight, diff --git a/packages/commonwealth/server/controllers/server_communities_controller.ts b/packages/commonwealth/server/controllers/server_communities_controller.ts index 56bcda4d918..d0c1728f1e7 100644 --- a/packages/commonwealth/server/controllers/server_communities_controller.ts +++ b/packages/commonwealth/server/controllers/server_communities_controller.ts @@ -1,8 +1,4 @@ -import { - CommunityStakeAttributes, - DB, - TokenBalanceCache, -} from '@hicommonwealth/model'; +import { CommunityStakeAttributes, DB } from '@hicommonwealth/model'; import BanCache from '../util/banCheckCache'; import { CreateChainNodeOptions, @@ -58,11 +54,7 @@ import { * Implements methods related to communities */ export class ServerCommunitiesController { - constructor( - public models: DB, - public tokenBalanceCache: TokenBalanceCache, - public banCache: BanCache, - ) {} + constructor(public models: DB, public banCache: BanCache) {} async searchCommunities( options: SearchCommunitiesOptions, diff --git a/packages/commonwealth/server/controllers/server_communities_methods/update_community.ts b/packages/commonwealth/server/controllers/server_communities_methods/update_community.ts index fc08ffee1f3..851c9bbf904 100644 --- a/packages/commonwealth/server/controllers/server_communities_methods/update_community.ts +++ b/packages/commonwealth/server/controllers/server_communities_methods/update_community.ts @@ -4,7 +4,7 @@ import type { CommunityAttributes, CommunitySnapshotSpaceWithSpaceAttached, } from '@hicommonwealth/model'; -import { UserInstance, newNamespaceValidator } from '@hicommonwealth/model'; +import { UserInstance, commonProtocol } from '@hicommonwealth/model'; import { Op } from 'sequelize'; import { MixpanelCommunityInteractionEvent } from '../../../shared/analytics/types'; import { urlHasValidHTTPPrefix } from '../../../shared/utils'; @@ -242,8 +242,7 @@ export async function __updateCommunity( throw new AppError(Errors.NotAdmin); } - await newNamespaceValidator.validateNamespace( - this.tokenBalanceCache, + await commonProtocol.newNamespaceValidator.validateNamespace( namespace, transactionHash, ownerOfChain.address, diff --git a/packages/commonwealth/server/controllers/server_groups_controller.ts b/packages/commonwealth/server/controllers/server_groups_controller.ts index f7258bb2aa6..e1ce223824f 100644 --- a/packages/commonwealth/server/controllers/server_groups_controller.ts +++ b/packages/commonwealth/server/controllers/server_groups_controller.ts @@ -1,4 +1,4 @@ -import { DB, TokenBalanceCache } from '@hicommonwealth/model'; +import { DB } from '@hicommonwealth/model'; import BanCache from '../util/banCheckCache'; import { CreateGroupOptions, @@ -35,11 +35,7 @@ import { * Implements methods related to groups */ export class ServerGroupsController { - constructor( - public models: DB, - public tokenBalanceCache: TokenBalanceCache, - public banCache: BanCache, - ) {} + constructor(public models: DB, public banCache: BanCache) {} async refreshMembership( options: RefreshMembershipOptions, diff --git a/packages/commonwealth/server/controllers/server_groups_methods/refresh_community_memberships.ts b/packages/commonwealth/server/controllers/server_groups_methods/refresh_community_memberships.ts index 1788d006e2d..ffa7f33dc9f 100644 --- a/packages/commonwealth/server/controllers/server_groups_methods/refresh_community_memberships.ts +++ b/packages/commonwealth/server/controllers/server_groups_methods/refresh_community_memberships.ts @@ -7,6 +7,7 @@ import { MembershipAttributes, MembershipRejectReason, OptionsWithBalances, + tokenBalanceCache, } from '@hicommonwealth/model'; import moment from 'moment'; import { Op, Sequelize } from 'sequelize'; @@ -67,7 +68,7 @@ export async function __refreshCommunityMemberships( getBalancesOptions.map(async (options) => { let result: Balances = {}; try { - result = await this.tokenBalanceCache.getBalances({ + result = await tokenBalanceCache.getBalances({ ...options, cacheRefresh: false, // get cached balances }); diff --git a/packages/commonwealth/server/controllers/server_groups_methods/refresh_membership.ts b/packages/commonwealth/server/controllers/server_groups_methods/refresh_membership.ts index 57b1ca603f2..9884dae0e98 100644 --- a/packages/commonwealth/server/controllers/server_groups_methods/refresh_membership.ts +++ b/packages/commonwealth/server/controllers/server_groups_methods/refresh_membership.ts @@ -45,7 +45,6 @@ export async function __refreshMembership( const memberships = await refreshMembershipsForAddress( this.models, - this.tokenBalanceCache, address, groups, true, // use fresh balances diff --git a/packages/commonwealth/server/controllers/server_polls_controller.ts b/packages/commonwealth/server/controllers/server_polls_controller.ts index 959d697d562..ab1450bb3a8 100644 --- a/packages/commonwealth/server/controllers/server_polls_controller.ts +++ b/packages/commonwealth/server/controllers/server_polls_controller.ts @@ -1,7 +1,5 @@ import { DB } from '@hicommonwealth/model'; -import { TokenBalanceCache } from '@hicommonwealth/model'; - import { DeletePollOptions, DeletePollResult, @@ -23,7 +21,7 @@ import { * */ export class ServerPollsController { - constructor(public models: DB, public tokenBalanceCache: TokenBalanceCache) {} + constructor(public models: DB) {} async deletePoll(options: DeletePollOptions): Promise { return __deletePoll.call(this, options); diff --git a/packages/commonwealth/server/controllers/server_polls_methods/update_poll_vote.ts b/packages/commonwealth/server/controllers/server_polls_methods/update_poll_vote.ts index 958ec9c6e10..37fe3986985 100644 --- a/packages/commonwealth/server/controllers/server_polls_methods/update_poll_vote.ts +++ b/packages/commonwealth/server/controllers/server_polls_methods/update_poll_vote.ts @@ -70,7 +70,6 @@ export async function __updatePollVote( // check token balance threshold if needed const { isValid } = await validateTopicGroupsMembership( this.models, - this.tokenBalanceCache, thread.topic_id, poll.community_id, address, diff --git a/packages/commonwealth/server/controllers/server_threads_controller.ts b/packages/commonwealth/server/controllers/server_threads_controller.ts index 221b87d63a9..812978e67c4 100644 --- a/packages/commonwealth/server/controllers/server_threads_controller.ts +++ b/packages/commonwealth/server/controllers/server_threads_controller.ts @@ -1,7 +1,6 @@ import { DB } from '@hicommonwealth/model'; import BanCache from '../util/banCheckCache'; -import { TokenBalanceCache } from '@hicommonwealth/model'; import GlobalActivityCache from '../util/globalActivityCache'; import { @@ -66,7 +65,6 @@ import { export class ServerThreadsController { constructor( public models: DB, - public tokenBalanceCache: TokenBalanceCache, public banCache: BanCache, public globalActivityCache?: GlobalActivityCache, ) {} diff --git a/packages/commonwealth/server/controllers/server_threads_methods/create_thread.ts b/packages/commonwealth/server/controllers/server_threads_methods/create_thread.ts index 57fea118cc7..22d6695a4d1 100644 --- a/packages/commonwealth/server/controllers/server_threads_methods/create_thread.ts +++ b/packages/commonwealth/server/controllers/server_threads_methods/create_thread.ts @@ -152,7 +152,6 @@ export async function __createThread( if (!isAdmin) { const { isValid, message } = await validateTopicGroupsMembership( this.models, - this.tokenBalanceCache, topicId, community.id, address, diff --git a/packages/commonwealth/server/controllers/server_threads_methods/create_thread_comment.ts b/packages/commonwealth/server/controllers/server_threads_methods/create_thread_comment.ts index 8ecebdb541a..dba6d7beeea 100644 --- a/packages/commonwealth/server/controllers/server_threads_methods/create_thread_comment.ts +++ b/packages/commonwealth/server/controllers/server_threads_methods/create_thread_comment.ts @@ -136,7 +136,6 @@ export async function __createThreadComment( if (!isAdmin) { const { isValid, message } = await validateTopicGroupsMembership( this.models, - this.tokenBalanceCache, thread.topic_id, thread.community_id, address, diff --git a/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts b/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts index ba627f7dc28..c2894091d21 100644 --- a/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts +++ b/packages/commonwealth/server/controllers/server_threads_methods/create_thread_reaction.ts @@ -7,7 +7,7 @@ import { AddressInstance, ReactionAttributes, UserInstance, - contractHelpers, + commonProtocol as commonProtocolService, } from '@hicommonwealth/model'; import { REACTION_WEIGHT_OVERRIDE } from 'server/config'; import { MixpanelCommunityInteractionEvent } from '../../../shared/analytics/types'; @@ -88,7 +88,6 @@ export async function __createThreadReaction( if (!isAdmin) { const { isValid, message } = await validateTopicGroupsMembership( this.models, - this.tokenBalanceCache, thread.topic_id, thread.community_id, address, @@ -114,14 +113,14 @@ export async function __createThreadReaction( if (!community) { throw new AppError(Errors.CommunityNotFound); } - const stakeBalance = await contractHelpers.getNamespaceBalance( - this.tokenBalanceCache, - community.namespace, - stake.stake_id, - commonProtocol.ValidChains.Sepolia, - address.address, - this.models, - ); + const stakeBalance = + await commonProtocolService.contractHelpers.getNamespaceBalance( + community.namespace, + stake.stake_id, + commonProtocol.ValidChains.Sepolia, + address.address, + this.models, + ); calculatedVotingWeight = commonProtocol.calculateVoteWeight( stakeBalance, voteWeight, diff --git a/packages/commonwealth/server/routes/communities/create_community_stakes_handler.ts b/packages/commonwealth/server/routes/communities/create_community_stakes_handler.ts index 91b60240292..ad400626e9b 100644 --- a/packages/commonwealth/server/routes/communities/create_community_stakes_handler.ts +++ b/packages/commonwealth/server/routes/communities/create_community_stakes_handler.ts @@ -2,7 +2,7 @@ import { AppError } from '@hicommonwealth/core'; import { CommunityStakeAttributes, DB, - communityStakeConfigValidator, + commonProtocol, } from '@hicommonwealth/model'; import { SetCommunityStakeBodySchema, @@ -65,7 +65,7 @@ export const createCommunityStakeHandler = async ( attributes: ['namespace'], }); - await communityStakeConfigValidator.validateCommunityStakeConfig( + await commonProtocol.communityStakeConfigValidator.validateCommunityStakeConfig( community, stake_id, ); diff --git a/packages/commonwealth/server/routing/router.ts b/packages/commonwealth/server/routing/router.ts index 6844189f35c..705aaf22078 100644 --- a/packages/commonwealth/server/routing/router.ts +++ b/packages/commonwealth/server/routing/router.ts @@ -6,8 +6,6 @@ import { createCommunityStakeHandler } from '../routes/communities/create_commun import { getCommunityStakeHandler } from '../routes/communities/get_community_stakes_handler'; import ddd from '../routes/ddd'; -import { TokenBalanceCache } from '@hicommonwealth/model'; - import { methodNotAllowedMiddleware, registerRoute, @@ -204,22 +202,15 @@ function setupRouter( app: Express, models: DB, viewCountCache: ViewCountCache, - tokenBalanceCache: TokenBalanceCache, banCache: BanCache, globalActivityCache: GlobalActivityCache, databaseValidationService: DatabaseValidationService, ) { // controllers const serverControllers: ServerControllers = { - threads: new ServerThreadsController( - models, - tokenBalanceCache, - banCache, - globalActivityCache, - ), + threads: new ServerThreadsController(models, banCache, globalActivityCache), comments: new ServerCommentsController( models, - tokenBalanceCache, banCache, globalActivityCache, ), @@ -227,14 +218,10 @@ function setupRouter( notifications: new ServerNotificationsController(models), analytics: new ServerAnalyticsController(), profiles: new ServerProfilesController(models), - communities: new ServerCommunitiesController( - models, - tokenBalanceCache, - banCache, - ), - polls: new ServerPollsController(models, tokenBalanceCache), + communities: new ServerCommunitiesController(models, banCache), + polls: new ServerPollsController(models), proposals: new ServerProposalsController(models), - groups: new ServerGroupsController(models, tokenBalanceCache, banCache), + groups: new ServerGroupsController(models, banCache), topics: new ServerTopicsController(models, banCache), admin: new ServerAdminController(models), }; diff --git a/packages/commonwealth/server/util/requirementsModule/refreshMembershipsForAddress.ts b/packages/commonwealth/server/util/requirementsModule/refreshMembershipsForAddress.ts index 49a98aced3c..e0955c6a594 100644 --- a/packages/commonwealth/server/util/requirementsModule/refreshMembershipsForAddress.ts +++ b/packages/commonwealth/server/util/requirementsModule/refreshMembershipsForAddress.ts @@ -5,7 +5,7 @@ import { MembershipAttributes, MembershipInstance, OptionsWithBalances, - TokenBalanceCache, + tokenBalanceCache, } from '@hicommonwealth/model'; import moment from 'moment'; import { FindOptions, Op, Sequelize } from 'sequelize'; @@ -23,7 +23,6 @@ import validateGroupMembership from './validateGroupMembership'; */ export async function refreshMembershipsForAddress( models: DB, - tokenBalanceCache: TokenBalanceCache, address: AddressAttributes, groups: GroupAttributes[], cacheRefresh: boolean, diff --git a/packages/commonwealth/server/util/requirementsModule/validateTopicGroupsMembership.ts b/packages/commonwealth/server/util/requirementsModule/validateTopicGroupsMembership.ts index bdb76a342be..9ad2e32c1ea 100644 --- a/packages/commonwealth/server/util/requirementsModule/validateTopicGroupsMembership.ts +++ b/packages/commonwealth/server/util/requirementsModule/validateTopicGroupsMembership.ts @@ -2,7 +2,6 @@ import { AddressAttributes, DB, MembershipRejectReason, - TokenBalanceCache, } from '@hicommonwealth/model'; import { Op } from 'sequelize'; import { refreshMembershipsForAddress } from './refreshMembershipsForAddress'; @@ -11,7 +10,6 @@ import { refreshMembershipsForAddress } from './refreshMembershipsForAddress'; * Validates if a given user address passes a set of requirements and grants access for * all groups of the given topic. * @param models DB handle - * @param tokenBalanceCache Token balance cache handle (new implementation) * @param topicId ID of the topic * @param communityId ID of the community of the groups * @param address Address to check against requirements @@ -19,7 +17,6 @@ import { refreshMembershipsForAddress } from './refreshMembershipsForAddress'; */ export async function validateTopicGroupsMembership( models: DB, - tokenBalanceCache: TokenBalanceCache, topicId: number, communityId: string, address: AddressAttributes, @@ -51,7 +48,6 @@ export async function validateTopicGroupsMembership( const memberships = await refreshMembershipsForAddress( models, - tokenBalanceCache, address, groups, false, // use cached balances diff --git a/packages/commonwealth/test/devnet/cosmos/tokenBalanceFetching.spec.ts b/packages/commonwealth/test/devnet/cosmos/tokenBalanceFetching.spec.ts index 454c56e2b25..700b12a8569 100644 --- a/packages/commonwealth/test/devnet/cosmos/tokenBalanceFetching.spec.ts +++ b/packages/commonwealth/test/devnet/cosmos/tokenBalanceFetching.spec.ts @@ -19,11 +19,7 @@ import { cache, delay, } from '@hicommonwealth/core'; -import { - TokenBalanceCache, - getTendermintClient, - models, -} from '@hicommonwealth/model'; +import { models, tokenBalanceCache } from '@hicommonwealth/model'; import BN from 'bn.js'; import { use as chaiUse, expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -91,7 +87,6 @@ async function generateCosmosAddresses(numberOfAddresses: number) { describe('Token Balance Cache Cosmos Tests', function () { this.timeout(80_000); - let tbc: TokenBalanceCache; // mnemonic + token allocation can be found in cosmos-chain-test/[version]/bootstrap.sh files const cosmosChainId = 'csdkv1ci'; const addressOne = 'cosmos1zf45elxg5alxxeewvumpprfqtxmy2ufhzvetgx'; @@ -104,12 +99,11 @@ describe('Token Balance Cache Cosmos Tests', function () { const redisCache = new RedisCache(); await redisCache.init('redis://localhost:6379'); cache(redisCache); - tbc = new TokenBalanceCache(models); }); describe('Cosmos Native', function () { it('should return a single balance', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CosmosNative, addresses: [addressOne], sourceOptions: { @@ -124,7 +118,7 @@ describe('Token Balance Cache Cosmos Tests', function () { }); it('should not throw if a single address fails', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CosmosNative, addresses: [addressOne, discobotAddress], sourceOptions: { @@ -138,7 +132,7 @@ describe('Token Balance Cache Cosmos Tests', function () { }); it('should only return a single result per address', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CosmosNative, addresses: [addressOne, addressOne], sourceOptions: { @@ -152,7 +146,7 @@ describe('Token Balance Cache Cosmos Tests', function () { }); it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CosmosNative, addresses: [addressOne, addressTwo], sourceOptions: { @@ -170,7 +164,7 @@ describe('Token Balance Cache Cosmos Tests', function () { const bulkAddresses = await generateCosmosAddresses(20); bulkAddresses.splice(4, 0, addressOne); bulkAddresses.splice(5, 0, addressTwo); - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CosmosNative, addresses: bulkAddresses, sourceOptions: { @@ -189,7 +183,6 @@ describe('Token Balance Cache Cosmos Tests', function () { const balanceTTL = 20; before('Set TBC caching TTL and reset Redis', async () => { - tbc = new TokenBalanceCache(models, balanceTTL); await cache().flushAll(); }); @@ -211,14 +204,17 @@ describe('Token Balance Cache Cosmos Tests', function () { const { amount } = await api.bank.balance(addressOne, denom); const originalAddressOneBalance = amount; - const balance = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.CosmosNative, - addresses: [addressOne], - sourceOptions: { - cosmosChainId, + const balance = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.CosmosNative, + addresses: [addressOne], + sourceOptions: { + cosmosChainId, + }, + cacheRefresh: true, }, - cacheRefresh: true, - }); + balanceTTL, + ); expect(Object.keys(balance).length).to.equal(1); expect(balance[addressOne]).to.equal(originalAddressOneBalance); @@ -236,24 +232,30 @@ describe('Token Balance Cache Cosmos Tests', function () { gas: '200000', }); - const balanceTwo = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.CosmosNative, - addresses: [addressOne], - sourceOptions: { - cosmosChainId, + const balanceTwo = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.CosmosNative, + addresses: [addressOne], + sourceOptions: { + cosmosChainId, + }, }, - }); + balanceTTL, + ); expect(Object.keys(balanceTwo).length).to.equal(1); expect(balanceTwo[addressOne]).to.equal(originalAddressOneBalance); await delay(20000); - const balanceThree = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.CosmosNative, - addresses: [addressOne], - sourceOptions: { - cosmosChainId, + const balanceThree = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.CosmosNative, + addresses: [addressOne], + sourceOptions: { + cosmosChainId, + }, }, - }); + balanceTTL, + ); const finalBn = new BN(originalAddressOneBalance); expect(Object.keys(balanceThree).length).to.equal(1); expect(balanceThree[addressOne]).to.equal( @@ -276,7 +278,7 @@ describe('Token Balance Cache Cosmos Tests', function () { }); it('should return a single cw721 balance', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CW721, addresses: [addressWithNft], sourceOptions: { @@ -290,7 +292,7 @@ describe('Token Balance Cache Cosmos Tests', function () { expect(balance[addressWithNft]).to.equal('1'); }); it('should return many cw721 balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CW721, addresses: [addressWithNft, addressWithoutNft], sourceOptions: { @@ -305,7 +307,7 @@ describe('Token Balance Cache Cosmos Tests', function () { expect(balances[addressWithoutNft]).to.equal('0'); }); it('should not throw if a single address fails', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CW721, addresses: [discobotAddress], sourceOptions: { @@ -318,7 +320,7 @@ describe('Token Balance Cache Cosmos Tests', function () { expect(Object.keys(balance).length).to.equal(0); }); it('should not throw if a single address out of many fails', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CW721, addresses: [addressWithNft, discobotAddress], sourceOptions: { @@ -335,7 +337,7 @@ describe('Token Balance Cache Cosmos Tests', function () { const bulkAddresses = await generateCosmosAddresses(20); bulkAddresses.splice(4, 0, addressWithNft); bulkAddresses.splice(5, 0, addressWithoutNft); - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.CW721, addresses: bulkAddresses, sourceOptions: { @@ -354,7 +356,6 @@ describe('Token Balance Cache Cosmos Tests', function () { const balanceTTL = 20; before('Set TBC caching TTL and reset Redis', async () => { - tbc = new TokenBalanceCache(models, balanceTTL); await cache().flushAll(); }); @@ -364,7 +365,7 @@ describe('Token Balance Cache Cosmos Tests', function () { cosmos_chain_id: stargazeChainId, }, }); - const tmClient = await getTendermintClient({ + const tmClient = await tokenBalanceCache.getTendermintClient({ chainNode, }); const api = QueryClient.withExtensions(tmClient, setupWasmExtension); @@ -382,29 +383,35 @@ describe('Token Balance Cache Cosmos Tests', function () { ); const expectedAddressOneBalance = response.tokens.length.toString(); - const balance = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.CW721, - addresses: [addressWithNft], - sourceOptions: { - cosmosChainId: stargazeChainId, - contractAddress, + const balance = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.CW721, + addresses: [addressWithNft], + sourceOptions: { + cosmosChainId: stargazeChainId, + contractAddress, + }, + cacheRefresh: true, }, - cacheRefresh: true, - }); + balanceTTL, + ); expect(Object.keys(balance).length).to.equal(1); expect(balance[addressWithNft]).to.equal(expectedAddressOneBalance); delay(20000); - const balanceAfterTTL = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.CW721, - addresses: [addressWithNft], - sourceOptions: { - cosmosChainId: stargazeChainId, - contractAddress, + const balanceAfterTTL = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.CW721, + addresses: [addressWithNft], + sourceOptions: { + cosmosChainId: stargazeChainId, + contractAddress, + }, }, - }); + balanceTTL, + ); expect(Object.keys(balanceAfterTTL).length).to.equal(1); expect(balanceAfterTTL[addressWithNft]).to.equal( diff --git a/packages/commonwealth/test/devnet/evm/tokenBalanceFetching.spec.ts b/packages/commonwealth/test/devnet/evm/tokenBalanceFetching.spec.ts index 3b060a51293..f44fef08a60 100644 --- a/packages/commonwealth/test/devnet/evm/tokenBalanceFetching.spec.ts +++ b/packages/commonwealth/test/devnet/evm/tokenBalanceFetching.spec.ts @@ -5,7 +5,7 @@ import { cache, delay, } from '@hicommonwealth/core'; -import { Balances, TokenBalanceCache, models } from '@hicommonwealth/model'; +import { Balances, models, tokenBalanceCache } from '@hicommonwealth/model'; import BN from 'bn.js'; import { expect } from 'chai'; import Web3 from 'web3'; @@ -67,7 +67,6 @@ function checkZeroBalances(balances: Balances, skipAddress: string[]) { describe('Token Balance Cache EVM Tests', function () { this.timeout(160000); - let tbc: TokenBalanceCache; const sdk = new ChainTesting('http://127.0.0.1:3000'); const addressOne = '0xCEB3C3D4B78d5d10bd18930DC0757ddB588A862a'; @@ -86,7 +85,6 @@ describe('Token Balance Cache EVM Tests', function () { const redisCache = new RedisCache(); await redisCache.init('redis://localhost:6379'); cache(redisCache); - tbc = new TokenBalanceCache(models); }); describe('ERC20', () => { @@ -118,7 +116,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('Single address', () => { it('should not fail if no address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [], sourceOptions: { @@ -132,7 +130,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not fail if a single invalid address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [discobotAddress], sourceOptions: { @@ -146,7 +144,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should return a single balance', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [addressOne], sourceOptions: { @@ -163,7 +161,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('on-chain batching', () => { it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [addressOne, addressTwo], sourceOptions: { @@ -179,7 +177,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not throw if a single address fails', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [addressOne, discobotAddress, addressTwo], sourceOptions: { @@ -195,7 +193,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should correctly batch balance requests', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: bulkAddresses, sourceOptions: { @@ -230,7 +228,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [addressOne, addressTwo], sourceOptions: { @@ -246,7 +244,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not throw if a single address fails', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: [addressOne, discobotAddress, addressTwo], sourceOptions: { @@ -263,7 +261,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should correctly batch balance requests', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC20, addresses: bulkAddresses, sourceOptions: { @@ -306,7 +304,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('Single address', () => { it('should not fail if no address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [], sourceOptions: { @@ -318,7 +316,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not fail if a single invalid address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [discobotAddress], sourceOptions: { @@ -331,7 +329,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should return a single balance', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [addressOne], sourceOptions: { @@ -347,7 +345,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('on-chain batching', () => { it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [addressOne, addressTwo], sourceOptions: { @@ -362,7 +360,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not throw if a single address fails', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [addressOne, discobotAddress, addressTwo], sourceOptions: { @@ -377,7 +375,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should correctly batch balance requests', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: bulkAddresses, sourceOptions: { @@ -411,7 +409,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [addressOne, addressTwo], sourceOptions: { @@ -426,7 +424,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not throw if a single address fails', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: [addressOne, discobotAddress, addressTwo], sourceOptions: { @@ -441,7 +439,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should correctly batch balance requests', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ETHNative, addresses: bulkAddresses, sourceOptions: { @@ -475,7 +473,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('Single address', () => { it('should not fail if no address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC721, addresses: [], sourceOptions: { @@ -489,7 +487,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not fail if a single invalid address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC721, addresses: [discobotAddress], sourceOptions: { @@ -503,7 +501,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should return a single balance', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC721, addresses: [addressOne721], sourceOptions: { @@ -520,7 +518,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('off-chain batching', () => { it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC721, addresses: [addressOne721, addressTwo721], sourceOptions: { @@ -536,7 +534,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not throw if a single address fails', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC721, addresses: [addressOne721, discobotAddress, addressTwo721], sourceOptions: { @@ -556,7 +554,7 @@ describe('Token Balance Cache EVM Tests', function () { bulkAddresses721.splice(4, 0, addressOne721); bulkAddresses721.splice(5, 0, addressTwo721); - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC721, addresses: bulkAddresses721, sourceOptions: { @@ -588,7 +586,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('Single address', () => { it('should not fail if no address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [], sourceOptions: { @@ -603,7 +601,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not fail if a single invalid address is given', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [discobotAddress], sourceOptions: { @@ -618,7 +616,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should return a single balance', async () => { - const balance = await tbc.getBalances({ + const balance = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [addressOne], sourceOptions: { @@ -632,7 +630,7 @@ describe('Token Balance Cache EVM Tests', function () { expect(Object.keys(balance).length).to.equal(1); expect(balance[addressOne]).to.equal('10'); - const balanceTwo = await tbc.getBalances({ + const balanceTwo = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [addressTwo], sourceOptions: { @@ -650,7 +648,7 @@ describe('Token Balance Cache EVM Tests', function () { describe('on-chain batching', () => { it('should return many balances', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [addressOne, addressTwo], sourceOptions: { @@ -667,7 +665,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should not throw if a single address fails', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: [addressOne, discobotAddress, addressTwo], sourceOptions: { @@ -684,7 +682,7 @@ describe('Token Balance Cache EVM Tests', function () { }); it('should correctly batch balance requests', async () => { - const balances = await tbc.getBalances({ + const balances = await tokenBalanceCache.getBalances({ balanceSourceType: BalanceSourceType.ERC1155, addresses: bulkAddresses, sourceOptions: { @@ -712,7 +710,6 @@ describe('Token Balance Cache EVM Tests', function () { before('Set TBC caching TTL and reset chain node', async () => { await resetChainNode(ethChainId); - tbc = new TokenBalanceCache(models, balanceTTL); // clear all Redis keys await cache().flushAll(); }); @@ -723,29 +720,35 @@ describe('Token Balance Cache EVM Tests', function () { addressOne, ); - const balance = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.ERC20, - addresses: [addressOne], - sourceOptions: { - evmChainId: ethChainId, - contractAddress: chainLinkAddress, + const balance = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.ERC20, + addresses: [addressOne], + sourceOptions: { + evmChainId: ethChainId, + contractAddress: chainLinkAddress, + }, + cacheRefresh: true, }, - cacheRefresh: true, - }); + balanceTTL, + ); expect(Object.keys(balance).length).to.equal(1); expect(balance[addressOne]).to.equal(originalAddressOneBalance); // this must complete in under balanceTTL time or the test fails await sdk.getErc20(chainLinkAddress, addressOne, transferAmount); - const balanceTwo = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.ERC20, - addresses: [addressOne], - sourceOptions: { - evmChainId: ethChainId, - contractAddress: chainLinkAddress, + const balanceTwo = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.ERC20, + addresses: [addressOne], + sourceOptions: { + evmChainId: ethChainId, + contractAddress: chainLinkAddress, + }, }, - }); + balanceTTL, + ); expect(Object.keys(balanceTwo).length).to.equal(1); expect(balanceTwo[addressOne]).to.equal(originalAddressOneBalance); @@ -756,14 +759,17 @@ describe('Token Balance Cache EVM Tests', function () { .add(transferAmountBN) .toString(10); - const balanceThree = await tbc.getBalances({ - balanceSourceType: BalanceSourceType.ERC20, - addresses: [addressOne], - sourceOptions: { - evmChainId: ethChainId, - contractAddress: chainLinkAddress, + const balanceThree = await tokenBalanceCache.getBalances( + { + balanceSourceType: BalanceSourceType.ERC20, + addresses: [addressOne], + sourceOptions: { + evmChainId: ethChainId, + contractAddress: chainLinkAddress, + }, }, - }); + balanceTTL, + ); expect(Object.keys(balanceThree).length).to.equal(1); expect(balanceThree[addressOne]).to.equal(finalAddressOneBalance); }); diff --git a/packages/commonwealth/test/integration/api/chainNodes.spec.ts b/packages/commonwealth/test/integration/api/chainNodes.spec.ts index 2d7ca0657aa..d129eb900e2 100644 --- a/packages/commonwealth/test/integration/api/chainNodes.spec.ts +++ b/packages/commonwealth/test/integration/api/chainNodes.spec.ts @@ -10,7 +10,7 @@ describe('ChainNode Tests', () => { }); it('Creates new ChainNode when', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 1, isAdmin: true }, diff --git a/packages/commonwealth/test/integration/api/communityStake.spec.ts b/packages/commonwealth/test/integration/api/communityStake.spec.ts index ef932ef05d1..f545a6a9847 100644 --- a/packages/commonwealth/test/integration/api/communityStake.spec.ts +++ b/packages/commonwealth/test/integration/api/communityStake.spec.ts @@ -1,6 +1,6 @@ import { UserInstance, - communityStakeConfigValidator, + commonProtocol, models, tester, } from '@hicommonwealth/model'; @@ -39,7 +39,7 @@ describe('POST communityStakes Tests', () => { }); it('The handler creates and updates community stake', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 1, isAdmin: true }, @@ -146,7 +146,7 @@ describe('POST communityStakes Tests', () => { ], attributes: ['namespace'], }); - await communityStakeConfigValidator.validateCommunityStakeConfig( + await commonProtocol.communityStakeConfigValidator.validateCommunityStakeConfig( community, 2, ); diff --git a/packages/commonwealth/test/integration/api/createChain.spec.ts b/packages/commonwealth/test/integration/api/createChain.spec.ts index f82e2e804ea..88ae38844f2 100644 --- a/packages/commonwealth/test/integration/api/createChain.spec.ts +++ b/packages/commonwealth/test/integration/api/createChain.spec.ts @@ -6,7 +6,7 @@ import { buildUser } from '../../unit/unitHelpers'; describe('create chain tests', () => { it('fails when no eth_chain_id is provided when chain is ethereum', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 1, isAdmin: true }, @@ -28,7 +28,7 @@ describe('create chain tests', () => { }); it('fails when eth_chain_id is not a number', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 1, isAdmin: true }, diff --git a/packages/commonwealth/test/integration/api/getRelatedCommunities.spec.ts b/packages/commonwealth/test/integration/api/getRelatedCommunities.spec.ts index 28f425a4d76..7e2623f3c45 100644 --- a/packages/commonwealth/test/integration/api/getRelatedCommunities.spec.ts +++ b/packages/commonwealth/test/integration/api/getRelatedCommunities.spec.ts @@ -8,7 +8,7 @@ describe('GetRelatedCommunities Tests', () => { }); it('Correctly returns nothing if base does not match chainNode', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const response = await controller.getRelatedCommunities({ chainNodeId: -100, }); @@ -17,7 +17,7 @@ describe('GetRelatedCommunities Tests', () => { }); it('Correctly returns results if base matches some chainNode.name', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const response = await controller.getRelatedCommunities({ chainNodeId: 2 }); assert.equal(response.length, 3); diff --git a/packages/commonwealth/test/integration/api/updateChain.spec.ts b/packages/commonwealth/test/integration/api/updateChain.spec.ts index be153eb7d71..c5f73fc4bef 100644 --- a/packages/commonwealth/test/integration/api/updateChain.spec.ts +++ b/packages/commonwealth/test/integration/api/updateChain.spec.ts @@ -1,7 +1,6 @@ import { ChainBase, ChainType } from '@hicommonwealth/core'; import { CommunityAttributes, - TokenBalanceCache, UserInstance, models, tester, @@ -30,7 +29,7 @@ describe('UpdateChain Tests', () => { }); it('Correctly updates chain', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 1, isAdmin: true }, @@ -62,7 +61,7 @@ describe('UpdateChain Tests', () => { }); it('Fails if namespace present but no transaction hash', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 2, isAdmin: false }, @@ -81,7 +80,7 @@ describe('UpdateChain Tests', () => { }); it('Fails if chain node of community does not match supported chain', async () => { - const controller = new ServerCommunitiesController(models, null, null); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 2, isAdmin: false }, @@ -109,11 +108,7 @@ describe('UpdateChain Tests', () => { }, }; - const controller = new ServerCommunitiesController( - models, - tbc as unknown as TokenBalanceCache, - null, - ); + const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ models, userAttributes: { email: '', id: 2, isAdmin: false }, diff --git a/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts index 2fa7076231a..e475aed1fec 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts @@ -1,5 +1,5 @@ import { NotificationCategories } from '@hicommonwealth/core'; -import { CommunityInstance, contractHelpers } from '@hicommonwealth/model'; +import { CommunityInstance, commonProtocol } from '@hicommonwealth/model'; import { expect } from 'chai'; import { ServerCommentsController } from 'server/controllers/server_comments_controller'; import { SearchCommentsOptions } from 'server/controllers/server_comments_methods/search_comments'; @@ -8,7 +8,9 @@ import { BAN_CACHE_MOCK_FN } from 'test/util/banCacheMock'; describe('ServerCommentsController', () => { beforeEach(() => { - Sinon.stub(contractHelpers, 'getNamespaceBalance').resolves('0'); + Sinon.stub(commonProtocol.contractHelpers, 'getNamespaceBalance').resolves( + '0', + ); }); afterEach(() => { Sinon.restore(); @@ -75,7 +77,6 @@ describe('ServerCommentsController', () => { }), }, }; - const tokenBalanceCache = {}; const banCache = BAN_CACHE_MOCK_FN('ethereum'); const user = { @@ -91,7 +92,6 @@ describe('ServerCommentsController', () => { const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -185,7 +185,6 @@ describe('ServerCommentsController', () => { }), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: sandbox.stub().resolves([true, null]), }; @@ -198,7 +197,6 @@ describe('ServerCommentsController', () => { const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -250,7 +248,6 @@ describe('ServerCommentsController', () => { findOne: sandbox.stub().resolves(null), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: sandbox.stub().resolves([true, null]), }; @@ -263,7 +260,6 @@ describe('ServerCommentsController', () => { const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -317,7 +313,6 @@ describe('ServerCommentsController', () => { }), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: sandbox.stub().resolves([false, 'big ban err']), }; @@ -331,7 +326,6 @@ describe('ServerCommentsController', () => { const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -442,9 +436,6 @@ describe('ServerCommentsController', () => { bulkCreate: sandbox.stub().resolves([]), }, }; - const tokenBalanceCache = { - getBalances: sandbox.stub().resolves({}), - }; const banCache = { checkBan: sandbox.stub().resolves([true, null]), }; @@ -460,7 +451,6 @@ describe('ServerCommentsController', () => { const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -489,12 +479,10 @@ describe('ServerCommentsController', () => { }, }, }; - const tokenBalanceCache = {}; const banCache = {}; const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -553,13 +541,11 @@ describe('ServerCommentsController', () => { findOne: async () => data, }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -631,11 +617,9 @@ describe('ServerCommentsController', () => { findOne: async () => data, }, }; - const tokenBalanceCache = {}; const banCache = BAN_CACHE_MOCK_FN('ethereum'); const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -682,13 +666,11 @@ describe('ServerCommentsController', () => { findOne: async () => null, }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -732,14 +714,12 @@ describe('ServerCommentsController', () => { destroy: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: () => [true, null], }; const serverCommentsController = new ServerCommentsController( db as any, - tokenBalanceCache as any, banCache as any, ); diff --git a/packages/commonwealth/test/unit/server_controllers/server_groups_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_groups_controller.spec.ts index 126f32415c6..ba88a219eae 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_groups_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_groups_controller.spec.ts @@ -130,13 +130,8 @@ const createMockedGroupsController = () => { transaction: async (callback) => callback(), }, }; - const tokenBalanceCache: any = {}; const banCache: any = {}; - const controller = new ServerGroupsController( - db, - tokenBalanceCache, - banCache, - ); + const controller = new ServerGroupsController(db, banCache); return controller; }; diff --git a/packages/commonwealth/test/unit/server_controllers/server_reactions_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_reactions_controller.spec.ts index bb3c07f4060..1e1e01441f8 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_reactions_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_reactions_controller.spec.ts @@ -1,4 +1,4 @@ -import { contractHelpers } from '@hicommonwealth/model'; +import { commonProtocol } from '@hicommonwealth/model'; import { expect } from 'chai'; import { ServerReactionsController } from 'server/controllers/server_reactions_controller'; import Sinon from 'sinon'; @@ -6,7 +6,9 @@ import { BAN_CACHE_MOCK_FN } from 'test/util/banCacheMock'; describe('ServerReactionsController', () => { beforeEach(() => { - Sinon.stub(contractHelpers, 'getNamespaceBalance').resolves('0'); + Sinon.stub(commonProtocol.contractHelpers, 'getNamespaceBalance').resolves( + '0', + ); }); afterEach(() => { Sinon.restore(); diff --git a/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts index 34a7ad70e5c..a369843d2df 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_threads_controller.spec.ts @@ -1,4 +1,4 @@ -import { contractHelpers } from '@hicommonwealth/model'; +import { commonProtocol } from '@hicommonwealth/model'; import { expect } from 'chai'; import { ServerThreadsController } from 'server/controllers/server_threads_controller'; import Sinon from 'sinon'; @@ -6,7 +6,9 @@ import { BAN_CACHE_MOCK_FN } from 'test/util/banCacheMock'; describe('ServerThreadsController', () => { beforeEach(() => { - Sinon.stub(contractHelpers, 'getNamespaceBalance').resolves('0'); + Sinon.stub(commonProtocol.contractHelpers, 'getNamespaceBalance').resolves( + '0', + ); }); afterEach(() => { Sinon.restore(); @@ -82,7 +84,6 @@ describe('ServerThreadsController', () => { }, }, }; - const tokenBalanceCache = {}; const user = { getAddresses: sandbox.stub().resolves([{ id: 1, verified: true }]), }; @@ -99,7 +100,6 @@ describe('ServerThreadsController', () => { const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -181,7 +181,6 @@ describe('ServerThreadsController', () => { findOne: sandbox.stub().resolves(null), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: sandbox.stub().resolves([true, null]), }; @@ -194,7 +193,6 @@ describe('ServerThreadsController', () => { const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -245,7 +243,6 @@ describe('ServerThreadsController', () => { }), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: sandbox.stub().resolves([true, null]), }; @@ -259,7 +256,6 @@ describe('ServerThreadsController', () => { const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -309,7 +305,6 @@ describe('ServerThreadsController', () => { }), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: sandbox.stub().resolves([false, 'big ban err']), }; @@ -323,7 +318,6 @@ describe('ServerThreadsController', () => { const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -437,9 +431,6 @@ describe('ServerThreadsController', () => { bulkCreate: sandbox.stub().resolves([]), }, }; - const tokenBalanceCache = { - getBalances: sandbox.stub().resolves({}), - }; const banCache = { checkBan: sandbox.stub().resolves([true, null]), }; @@ -455,7 +446,6 @@ describe('ServerThreadsController', () => { const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -539,14 +529,12 @@ describe('ServerThreadsController', () => { findAll: async () => [], }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -611,14 +599,12 @@ describe('ServerThreadsController', () => { create: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [false, 'big bad error'], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -683,14 +669,12 @@ describe('ServerThreadsController', () => { create: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -785,14 +769,12 @@ describe('ServerThreadsController', () => { create: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -847,14 +829,12 @@ describe('ServerThreadsController', () => { create: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -927,14 +907,12 @@ describe('ServerThreadsController', () => { create: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -1003,14 +981,12 @@ describe('ServerThreadsController', () => { create: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); @@ -1071,14 +1047,12 @@ describe('ServerThreadsController', () => { findAll: async () => [{}], // used in findOneRole }, }; - const tokenBalanceCache = {}; const banCache = BAN_CACHE_MOCK_FN('ethereum'); const address = { address: '0x123', }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -1120,13 +1094,11 @@ describe('ServerThreadsController', () => { destroy: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -1168,13 +1140,11 @@ describe('ServerThreadsController', () => { destroy: async () => ({}), }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [false, 'bad'], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -1215,13 +1185,11 @@ describe('ServerThreadsController', () => { findAll: async () => [{}], // used in findOneRole }, }; - const tokenBalanceCache = {}; const banCache = { checkBan: async () => [true, null], }; const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { @@ -1308,11 +1276,9 @@ describe('ServerThreadsController', () => { findAll: async () => [{}], // used in findOneRole }, }; - const tokenBalanceCache = {}; const banCache = BAN_CACHE_MOCK_FN('ethereum'); const serverThreadsController = new ServerThreadsController( db as any, - tokenBalanceCache as any, banCache as any, ); const user = { diff --git a/packages/commonwealth/test/unit/server_controllers/server_threads_controller.update_thread.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_threads_controller.update_thread.spec.ts index c03f10a84d5..cdc4d139cfe 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_threads_controller.update_thread.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_threads_controller.update_thread.spec.ts @@ -125,16 +125,11 @@ describe('ServerThreadsController', () => { }), }, }; - const tokenBalanceCache: any = {}; const banCache: any = { checkBan: () => [true, null], }; - const serverThreadsController = new ServerThreadsController( - db, - tokenBalanceCache, - banCache, - ); + const serverThreadsController = new ServerThreadsController(db, banCache); const [updatedThread, notificationOptions, analyticsOptions] = await serverThreadsController.updateThread(attributes); From 6f5ac5d4050c2976c426cc8b21963a0ec5868df8 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Tue, 13 Feb 2024 11:58:36 -0500 Subject: [PATCH 02/17] fix test --- .../test/integration/api/updateChain.spec.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/commonwealth/test/integration/api/updateChain.spec.ts b/packages/commonwealth/test/integration/api/updateChain.spec.ts index c5f73fc4bef..1f18b0ab506 100644 --- a/packages/commonwealth/test/integration/api/updateChain.spec.ts +++ b/packages/commonwealth/test/integration/api/updateChain.spec.ts @@ -4,8 +4,10 @@ import { UserInstance, models, tester, + tokenBalanceCache, } from '@hicommonwealth/model'; import { assert } from 'chai'; +import Sinon from 'sinon'; import { ServerCommunitiesController } from '../../../server/controllers/server_communities_controller'; import { Errors } from '../../../server/controllers/server_communities_methods/update_community'; import { buildUser } from '../../unit/unitHelpers'; @@ -102,11 +104,9 @@ describe('UpdateChain Tests', () => { // skipped because public chainNodes are unreliable. If you want to test this functionality, update the goleri // chainNode and do it locally. xit('Correctly updates namespace', async () => { - const tbc = { - getBalances: async (_: any) => { - return { '0x42D6716549A78c05FD8EF1f999D52751Bbf9F46a': '1' }; - }, - }; + Sinon.stub(tokenBalanceCache, 'getBalances').resolves({ + '0x42D6716549A78c05FD8EF1f999D52751Bbf9F46a': '1', + }); const controller = new ServerCommunitiesController(models, null); const user: UserInstance = buildUser({ @@ -130,5 +130,6 @@ describe('UpdateChain Tests', () => { }); assert.equal(response.namespace, 'IanSpace'); + Sinon.restore(); }); }); From 8fb41132956bccd2a8376e96aefc00c576f45af5 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Tue, 13 Feb 2024 16:42:55 -0500 Subject: [PATCH 03/17] add fix notes --- .../src/community/SetCommunityNamespace.command.ts | 10 +++++++--- .../server_communities_methods/create_community.ts | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/libs/model/src/community/SetCommunityNamespace.command.ts b/libs/model/src/community/SetCommunityNamespace.command.ts index 12bbd80bd47..977d80cdeed 100644 --- a/libs/model/src/community/SetCommunityNamespace.command.ts +++ b/libs/model/src/community/SetCommunityNamespace.command.ts @@ -4,6 +4,7 @@ import { models } from '../database'; import { isCommunityAdmin } from '../middleware'; import { mustExist } from '../middleware/guards'; import type { CommunityAttributes } from '../models'; +import { validateNamespace } from '../services/commonProtocol/newNamespaceValidator'; export const schema = z.object({ namespace: z.string(), @@ -22,10 +23,13 @@ export const SetCommunityNamespace: CommandMetadata< if (!mustExist('Community', community)) return; - // TODO: validate contract - // call protocol api and resolve if tbc should be a singleton + await validateNamespace( + payload.namespace, + payload.txHash, + payload.address, + community, + ); - //await validateNamespace(TokenBalanceCache, payload.namespace, payload.txHash, payload.address, community) community.namespace = payload.namespace; return (await community.save()).get({ plain: true }); }, diff --git a/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts b/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts index d6e7836f7a9..964d007119f 100644 --- a/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts +++ b/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts @@ -80,9 +80,12 @@ export async function __createCommunity( this: ServerCommunitiesController, { user, community }: CreateCommunityOptions, ): Promise { + // FIXME: this is taken care by authentication layer if (!user) { throw new AppError('Not signed in'); } + + // FIXME: this looks like a non-reusable custom authorization // require Admin privilege for creating Chain/DAO if ( community.type !== ChainType.Token && @@ -110,6 +113,7 @@ export async function __createCommunity( let sanitizedSpec; let hex; + // FIXME: this looks like input validation // always generate a chain id if (community.base === ChainBase.Ethereum) { if (!community.eth_chain_id || !+community.eth_chain_id) { @@ -123,6 +127,7 @@ export async function __createCommunity( community.base === ChainBase.Ethereum && community.type !== ChainType.Offchain ) { + // FIXME: this looks like input validation if (!Web3.utils.isAddress(community.address)) { throw new AppError(Errors.InvalidAddress); } @@ -272,6 +277,8 @@ export async function __createCommunity( token_name, user_address, } = community; + + // FIXME: this looks like input validation if (website && !urlHasValidHTTPPrefix(website)) { throw new AppError(Errors.InvalidWebsite); } else if (discord && !urlHasValidHTTPPrefix(discord)) { @@ -366,6 +373,7 @@ export async function __createCommunity( const nodeJSON = node.toJSON(); delete nodeJSON.private_url; + // FIXME: looks like state mutations start here, make sure we are using the same transaction await this.models.Topic.create({ community_id: createdCommunity.id, name: 'General', From 0ca94602495a1d80da2a972ec8985e0020eb7021 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Wed, 14 Feb 2024 10:24:36 -0500 Subject: [PATCH 04/17] fix imports --- libs/model/src/community/SetCommunityNamespace.command.ts | 4 ++-- libs/model/src/community/SetCommunityStake.command.ts | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/model/src/community/SetCommunityNamespace.command.ts b/libs/model/src/community/SetCommunityNamespace.command.ts index 977d80cdeed..7caaa3ab0b1 100644 --- a/libs/model/src/community/SetCommunityNamespace.command.ts +++ b/libs/model/src/community/SetCommunityNamespace.command.ts @@ -4,7 +4,7 @@ import { models } from '../database'; import { isCommunityAdmin } from '../middleware'; import { mustExist } from '../middleware/guards'; import type { CommunityAttributes } from '../models'; -import { validateNamespace } from '../services/commonProtocol/newNamespaceValidator'; +import { commonProtocol } from '../services'; export const schema = z.object({ namespace: z.string(), @@ -23,7 +23,7 @@ export const SetCommunityNamespace: CommandMetadata< if (!mustExist('Community', community)) return; - await validateNamespace( + await commonProtocol.newNamespaceValidator.validateNamespace( payload.namespace, payload.txHash, payload.address, diff --git a/libs/model/src/community/SetCommunityStake.command.ts b/libs/model/src/community/SetCommunityStake.command.ts index 97de8910aa5..888ded4ce68 100644 --- a/libs/model/src/community/SetCommunityStake.command.ts +++ b/libs/model/src/community/SetCommunityStake.command.ts @@ -4,7 +4,7 @@ import { models } from '../database'; import { isCommunityAdmin } from '../middleware'; import { mustExist } from '../middleware/guards'; import { CommunityAttributes } from '../models'; -import { validateCommunityStakeConfig } from '../services/commonProtocol/communityStakeConfigValidator'; +import { commonProtocol } from '../services'; const schema = z.object({ stake_id: z.coerce.number().int(), @@ -48,7 +48,10 @@ export const SetCommunityStake: CommandMetadata< ); // !domain, application, and infrastructure services (stateless, not related to entities or value objects) - await validateCommunityStakeConfig(community, payload.stake_id); + await commonProtocol.communityStakeConfigValidator.validateCommunityStakeConfig( + community, + payload.stake_id, + ); // !side effects const [updated] = await models.CommunityStake.upsert({ From a6f92740831b52c8b607b5e2a4cb5e05d8e70fe4 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Wed, 14 Feb 2024 17:10:28 -0500 Subject: [PATCH 05/17] add create group --- libs/model/src/community/CreateGroup.command.ts | 15 +++++++++++++++ ...pace.command.ts => UpdateCommunity.command.ts} | 2 +- libs/model/src/community/index.ts | 3 ++- .../create_community.ts | 1 + .../server_groups_methods/create_group.ts | 8 ++++++++ .../commonwealth/server/routes/ddd/community.ts | 6 ++++++ 6 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 libs/model/src/community/CreateGroup.command.ts rename libs/model/src/community/{SetCommunityNamespace.command.ts => UpdateCommunity.command.ts} (94%) diff --git a/libs/model/src/community/CreateGroup.command.ts b/libs/model/src/community/CreateGroup.command.ts new file mode 100644 index 00000000000..cad0778ee1b --- /dev/null +++ b/libs/model/src/community/CreateGroup.command.ts @@ -0,0 +1,15 @@ +import { CommandMetadata } from '@hicommonwealth/core'; +import { z } from 'zod'; +import { isCommunityAdmin } from '../middleware'; +import { CommunityAttributes } from '../models'; + +const schema = z.object({}); + +export const CreateGroup: CommandMetadata = + { + schema, + auth: [isCommunityAdmin], + body: async ({ payload }) => { + return payload; + }, + }; diff --git a/libs/model/src/community/SetCommunityNamespace.command.ts b/libs/model/src/community/UpdateCommunity.command.ts similarity index 94% rename from libs/model/src/community/SetCommunityNamespace.command.ts rename to libs/model/src/community/UpdateCommunity.command.ts index 7caaa3ab0b1..bdd233351d4 100644 --- a/libs/model/src/community/SetCommunityNamespace.command.ts +++ b/libs/model/src/community/UpdateCommunity.command.ts @@ -12,7 +12,7 @@ export const schema = z.object({ address: z.string(), }); -export const SetCommunityNamespace: CommandMetadata< +export const UpdateCommunity: CommandMetadata< CommunityAttributes, typeof schema > = { diff --git a/libs/model/src/community/index.ts b/libs/model/src/community/index.ts index c4206334aa3..41844cbc6f7 100644 --- a/libs/model/src/community/index.ts +++ b/libs/model/src/community/index.ts @@ -1,4 +1,5 @@ export * from './CreateCommunity.command'; +export * from './CreateGroup.command'; export * from './GetCommunityStake.query'; -export * from './SetCommunityNamespace.command'; export * from './SetCommunityStake.command'; +export * from './UpdateCommunity.command'; diff --git a/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts b/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts index 964d007119f..53828e7d456 100644 --- a/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts +++ b/packages/commonwealth/server/controllers/server_communities_methods/create_community.ts @@ -27,6 +27,7 @@ import { RoleInstanceWithPermission } from '../../util/roles'; import testSubstrateSpec from '../../util/testSubstrateSpec'; import { ServerCommunitiesController } from '../server_communities_controller'; +// FIXME: Probably part of zod validation export const Errors = { NoId: 'Must provide id', ReservedId: 'The id is reserved and cannot be used', diff --git a/packages/commonwealth/server/controllers/server_groups_methods/create_group.ts b/packages/commonwealth/server/controllers/server_groups_methods/create_group.ts index 32d81227c26..d0bc37f9557 100644 --- a/packages/commonwealth/server/controllers/server_groups_methods/create_group.ts +++ b/packages/commonwealth/server/controllers/server_groups_methods/create_group.ts @@ -17,6 +17,7 @@ import { ServerGroupsController } from '../server_groups_controller'; const MAX_GROUPS_PER_COMMUNITY = 20; +// FIXME: validation errors const Errors = { InvalidMetadata: 'Invalid metadata', InvalidRequirements: 'Invalid requirements', @@ -25,6 +26,7 @@ const Errors = { InvalidTopics: 'Invalid topics', }; +// FIXME: the schema export type CreateGroupOptions = { user: UserInstance; community: CommunityInstance; @@ -34,12 +36,14 @@ export type CreateGroupOptions = { topics?: number[]; }; +// FIXME: should be partial of the aggregate export type CreateGroupResult = [GroupAttributes, TrackOptions]; export async function __createGroup( this: ServerGroupsController, { user, community, metadata, requirements, topics }: CreateGroupOptions, ): Promise { + // FIXME: authorization const isAdmin = await validateOwner({ models: this.models, user, @@ -52,11 +56,13 @@ export async function __createGroup( throw new AppError(Errors.Unauthorized); } + // FIXME: validation const metadataValidationErr = validateMetadata(metadata); if (metadataValidationErr) { throw new AppError(`${Errors.InvalidMetadata}: ${metadataValidationErr}`); } + // FIXME: validation const requirementsValidationErr = validateRequirements(requirements); if (requirementsValidationErr) { throw new AppError( @@ -64,6 +70,7 @@ export async function __createGroup( ); } + // FIXME: invariant const numCommunityGroups = await this.models.Group.count({ where: { community_id: community.id, @@ -121,6 +128,7 @@ export async function __createGroup( }, ); + // FIXME: move to middleware const analyticsOptions = { event: MixpanelCommunityInteractionEvent.CREATE_GROUP, community: community.id, diff --git a/packages/commonwealth/server/routes/ddd/community.ts b/packages/commonwealth/server/routes/ddd/community.ts index 92f55a0b860..5327dfb7607 100644 --- a/packages/commonwealth/server/routes/ddd/community.ts +++ b/packages/commonwealth/server/routes/ddd/community.ts @@ -17,4 +17,10 @@ router.put( expressCommand(Community.SetCommunityStake), ); +router.post( + '/:id/group', + passport.authenticate('jwt', { session: false }), + expressCommand(Community.CreateGroup), +); + export default router; From c8f6391af67369b33c343b759579d7581570e019 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Wed, 14 Feb 2024 17:12:52 -0500 Subject: [PATCH 06/17] add test --- .../{CommunityStake.spec.ts => community-stake.spec.ts} | 0 libs/model/__tests__/community/group-lifecycle.spec.ts | 1 + 2 files changed, 1 insertion(+) rename libs/model/__tests__/community/{CommunityStake.spec.ts => community-stake.spec.ts} (100%) create mode 100644 libs/model/__tests__/community/group-lifecycle.spec.ts diff --git a/libs/model/__tests__/community/CommunityStake.spec.ts b/libs/model/__tests__/community/community-stake.spec.ts similarity index 100% rename from libs/model/__tests__/community/CommunityStake.spec.ts rename to libs/model/__tests__/community/community-stake.spec.ts diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts new file mode 100644 index 00000000000..5b4a88549d0 --- /dev/null +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -0,0 +1 @@ +describe('group lifecycle', () => {}); From 5c595c6db1c9fe4526df0b4fa25eae91831d0278 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Thu, 15 Feb 2024 14:39:44 -0500 Subject: [PATCH 07/17] add tests --- .../__tests__/community/group-lifecycle.spec.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts index 5b4a88549d0..062147fa0af 100644 --- a/libs/model/__tests__/community/group-lifecycle.spec.ts +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -1 +1,16 @@ -describe('group lifecycle', () => {}); +import { dispose } from '@hicommonwealth/core'; +import { tester } from '@hicommonwealth/model'; + +describe('group lifecycle', () => { + before(async () => { + await tester.seedDb(); + }); + + after(async () => { + await dispose()(); + }); + + it('should create group when none exists', () => {}); + + it('should fail creation when group with same id found', () => {}); +}); From 1d4d06e394514b3a3de2f18da42d937009aaab96 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Thu, 15 Feb 2024 17:03:39 -0500 Subject: [PATCH 08/17] add more tests --- libs/model/__tests__/community/group-lifecycle.spec.ts | 4 ++++ .../commonwealth/server/routes/groups/create_group_handler.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts index 062147fa0af..7c5918056d2 100644 --- a/libs/model/__tests__/community/group-lifecycle.spec.ts +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -13,4 +13,8 @@ describe('group lifecycle', () => { it('should create group when none exists', () => {}); it('should fail creation when group with same id found', () => {}); + + it('should fail creation when community reached max number of groups allowed', () => {}); + + it('should fail creation when sending invalid topics', () => {}); }); diff --git a/packages/commonwealth/server/routes/groups/create_group_handler.ts b/packages/commonwealth/server/routes/groups/create_group_handler.ts index 58e7a2e4ea8..3b8d0345b32 100644 --- a/packages/commonwealth/server/routes/groups/create_group_handler.ts +++ b/packages/commonwealth/server/routes/groups/create_group_handler.ts @@ -18,6 +18,7 @@ export const createGroupHandler = async ( ) => { const { user, address, community } = req; + // FIXME: this is the command schema const schema = z.object({ body: z.object({ metadata: z.object({ @@ -46,6 +47,7 @@ export const createGroupHandler = async ( topics, }); + // FIXME: keep for now, but should be a debounced async integration policy that get's triggered by creation events // refresh memberships in background controllers.groups .refreshCommunityMemberships({ @@ -54,6 +56,7 @@ export const createGroupHandler = async ( }) .catch(console.error); + // FIXME: replace with analytics middleware controllers.analytics.track(analyticsOptions, req).catch(console.error); return success(res, group); From 897cfef60015625285d98740067048931c528190 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 10:45:05 -0500 Subject: [PATCH 09/17] add schemas --- .../community/group-lifecycle.spec.ts | 94 +++++++++++++++++-- .../src/community/CreateGroup.command.ts | 21 ++++- .../src/community/Requirements.schema.ts | 49 ++++++++++ libs/model/src/models/community.ts | 7 +- libs/model/src/test/seedDb.ts | 5 - package.json | 2 + yarn.lock | 10 ++ 7 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 libs/model/src/community/Requirements.schema.ts diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts index 7c5918056d2..ac596f2efc0 100644 --- a/libs/model/__tests__/community/group-lifecycle.spec.ts +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -1,20 +1,98 @@ -import { dispose } from '@hicommonwealth/core'; -import { tester } from '@hicommonwealth/model'; +import { InvalidState, command, dispose } from '@hicommonwealth/core'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { Chance } from 'chance'; +import { CreateGroup, MAX_GROUPS_PER_COMMUNITY } from '../../src/community'; +import { seedDb } from '../../src/test'; + +chai.use(chaiAsPromised); +const chance = Chance(); + +describe('Group lifecycle', () => { + const context = { + id: 'common-protocol', + actor: { + user: { id: 2, email: '' }, + address_id: '0xtestAddress', + }, + payload: { + metadata: { + name: chance.name(), + description: chance.sentence(), + required_requirements: 1, + membership_ttl: 100, + }, + requirements: [], // TODO: + topics: [], // TODO: + }, + }; -describe('group lifecycle', () => { before(async () => { - await tester.seedDb(); + await seedDb(); }); after(async () => { await dispose()(); }); - it('should create group when none exists', () => {}); + it('should create group when none exists', async () => { + const results = await command(CreateGroup, context); + expect(results).to.deep.contain({ + groups: { ...context.payload.metadata }, + }); + }); - it('should fail creation when group with same id found', () => {}); + it('should fail creation when group with same id found', () => { + expect(command(CreateGroup, context)).to.eventually.be.rejectedWith( + InvalidState, + ); + }); - it('should fail creation when community reached max number of groups allowed', () => {}); + it('should fail creation when community reached max number of groups allowed', async () => { + // create max groups + for (let i = 0; i < MAX_GROUPS_PER_COMMUNITY; i++) + await command(CreateGroup, { + ...context, + payload: { + ...context.payload, + metadata: { name: chance.name(), description: chance.sentence() }, + }, + }); - it('should fail creation when sending invalid topics', () => {}); + const invalid = { + ...context, + payload: { + metadata: { + name: chance.name(), + description: chance.sentence(), + required_requirements: 1, + membership_ttl: 100, + }, + requirements: [], // TODO: + topics: [], // TODO: + }, + }; + expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( + InvalidState, + ); + }); + + it('should fail creation when sending invalid topics', () => { + const invalid = { + ...context, + payload: { + metadata: { + name: chance.name(), + description: chance.sentence(), + required_requirements: 1, + membership_ttl: 100, + }, + requirements: [], // TODO: + topics: [], // TODO: set invalid topic here + }, + }; + expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( + InvalidState, + ); + }); }); diff --git a/libs/model/src/community/CreateGroup.command.ts b/libs/model/src/community/CreateGroup.command.ts index cad0778ee1b..52670e30574 100644 --- a/libs/model/src/community/CreateGroup.command.ts +++ b/libs/model/src/community/CreateGroup.command.ts @@ -1,15 +1,28 @@ import { CommandMetadata } from '@hicommonwealth/core'; import { z } from 'zod'; -import { isCommunityAdmin } from '../middleware'; +import { isCommunityAdminOrModerator } from '../middleware'; import { CommunityAttributes } from '../models'; +import { Requirement } from './Requirements.schema'; -const schema = z.object({}); +const schema = z.object({ + metadata: z.object({ + name: z.string(), + description: z.string(), + required_requirements: z.number().optional(), + membership_ttl: z.number().optional(), + }), + requirements: z.array(Requirement), + topics: z.array(z.number()).optional(), +}); + +export const MAX_GROUPS_PER_COMMUNITY = 20; export const CreateGroup: CommandMetadata = { schema, - auth: [isCommunityAdmin], + auth: [isCommunityAdminOrModerator], body: async ({ payload }) => { - return payload; + console.log(payload); + return {}; }, }; diff --git a/libs/model/src/community/Requirements.schema.ts b/libs/model/src/community/Requirements.schema.ts new file mode 100644 index 00000000000..83f29d7902f --- /dev/null +++ b/libs/model/src/community/Requirements.schema.ts @@ -0,0 +1,49 @@ +import { z } from 'zod'; + +const ContractSource = z.object({ + source_type: z.enum(['erc20', 'erc721', 'erc1155']), + evm_chain_id: z.number(), + contract_address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), + token_id: z + .string() + .regex(/^[0-9]+$/) + .optional(), +}); + +const NativeSource = z.object({ + source_type: z.enum(['eth_native']), + evm_chain_id: z.number(), +}); + +const CosmosSource = z.object({ + source_type: z.enum(['cosmos_native']), + cosmos_chain_id: z.string(), + token_symbol: z.string(), +}); + +const CosmosContractSource = z.object({ + source_type: z.enum(['cw721']), + cosmos_chain_id: z.string(), + contract_address: z.string(), +}); + +const ThresholdData = z.object({ + threshold: z.string().regex(/^[0-9]+$/), + source: z.union([ + ContractSource, + NativeSource, + CosmosSource, + CosmosContractSource, + ]), +}); + +const AllowlistData = z.object({ + allow: z.array(z.string().regex(/^0x[a-fA-F0-9]{40}$/)), +}); + +const RuleData = z.union([ThresholdData, AllowlistData]); + +export const Requirement = z.object({ + rule: z.enum(['threshold', 'allow']), + data: RuleData, +}); diff --git a/libs/model/src/models/community.ts b/libs/model/src/models/community.ts index 497f5286817..673d03edeb6 100644 --- a/libs/model/src/models/community.ts +++ b/libs/model/src/models/community.ts @@ -12,6 +12,7 @@ import type { ChainNodeAttributes, ChainNodeInstance } from './chain_node'; import type { CommentAttributes } from './comment'; import { CommunityStakeAttributes } from './community_stake'; import type { ContractInstance } from './contract'; +import { GroupAttributes } from './group'; import type { StarredCommunityAttributes } from './starred_community'; import type { ThreadAttributes } from './thread'; import type { TopicAttributes, TopicInstance } from './topic'; @@ -67,6 +68,7 @@ export type CommunityAttributes = { ChainObjectVersion?: any; // TODO Contract?: ContractInstance; CommunityStakes?: CommunityStakeAttributes[]; + groups?: GroupAttributes[]; created_at?: Date; updated_at?: Date; @@ -194,7 +196,10 @@ export default ( through: models.CommunityContract, foreignKey: 'community_id', }); - models.Community.hasMany(models.Group, { foreignKey: 'community_id' }); + models.Community.hasMany(models.Group, { + as: 'groups', + foreignKey: 'community_id', + }); models.Community.hasMany(models.CommunityStake, { foreignKey: 'community_id', }); diff --git a/libs/model/src/test/seedDb.ts b/libs/model/src/test/seedDb.ts index 75de63ae5f2..7dd731acc3b 100644 --- a/libs/model/src/test/seedDb.ts +++ b/libs/model/src/test/seedDb.ts @@ -4,7 +4,6 @@ import { ChainNetwork, ChainType, NotificationCategories, - dispose, logger, } from '@hicommonwealth/core'; import { QueryTypes, Sequelize } from 'sequelize'; @@ -35,10 +34,6 @@ export const seedDb = async (debug = false): Promise => { // connect and seed const { models } = await import('..'); - // dispose sequelize connection before exit - dispose(async () => { - models.sequelize.close().catch((_) => {}); - }); await models.sequelize.sync({ force: true }); log.info('done syncing.'); diff --git a/package.json b/package.json index 42fb8eacf00..92d7737bac7 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@types/bs58": "^4.0.1", "@types/chai": "^4.2.11", "@types/chai-as-promised": "^7.1.8", + "@types/chance": "^1.1.6", "@types/cors": "^2.8.13", "@types/expect": "^24.3.0", "@types/express": "^4.17.21", @@ -104,6 +105,7 @@ "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "chai-http": "^4.3.0", + "chance": "^1.1.11", "chromatic": "^6.17.4", "concurrently": "^7.6.0", "copy-webpack-plugin": "^11.0.0", diff --git a/yarn.lock b/yarn.lock index fafada2dc3d..f2da11b8eda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5524,6 +5524,11 @@ resolved "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz" integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== +"@types/chance@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.6.tgz#2fe3de58742629602c3fbab468093b27207f04ad" + integrity sha512-V+pm3stv1Mvz8fSKJJod6CglNGVqEQ6OyuqitoDkWywEODM/eJd1eSuIp9xt6DrX8BWZ2eDSIzbw1tPCUTvGbQ== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz" @@ -8623,6 +8628,11 @@ chalk@^5.2.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== +chance@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.11.tgz#78e10e1f9220a5bbc60a83e3f28a5d8558d84d1b" + integrity sha512-kqTg3WWywappJPqtgrdvbA380VoXO2eu9VCV895JgbyHsaErXdyHK9LOZ911OvAk6L0obK7kDk9CGs8+oBawVA== + check-error@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz" From ce26442300d94cfc61c9a835c9c1d170543fa2a8 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 12:20:01 -0500 Subject: [PATCH 10/17] add dependency cruiser --- .dependency-cruiser.js | 464 +++++++++++++++++++++++++++++++++++++++++ .gitignore | 3 +- package.json | 2 + yarn.lock | 276 +++++++++++++++++++++--- 4 files changed, 716 insertions(+), 29 deletions(-) create mode 100644 .dependency-cruiser.js diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js new file mode 100644 index 00000000000..3ac427680f2 --- /dev/null +++ b/.dependency-cruiser.js @@ -0,0 +1,464 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +module.exports = { + forbidden: [ + // { + // name: 'no-circular', + // severity: 'warn', + // comment: + // 'This dependency is part of a circular relationship. You might want to revise ' + + // 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + // from: {}, + // to: { + // circular: true, + // }, + // }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + 'add an exception for it in your dependency-cruiser configuration. By default ' + + 'this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration ' + + 'files (.d.ts), tsconfig.json and some of the babel and webpack configs.', + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)[.][^/]+[.](js|cjs|mjs|ts|json)$', // dot files + '[.]d[.]ts$', // TypeScript declaration files + '(^|/)tsconfig[.]json$', // TypeScript config + '(^|/)(babel|webpack)[.]config[.](js|cjs|mjs|ts|json)$', // other configs + ], + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['core'], + path: [ + '^(v8/tools/codemap)$', + '^(v8/tools/consarray)$', + '^(v8/tools/csvparser)$', + '^(v8/tools/logreader)$', + '^(v8/tools/profile_view)$', + '^(v8/tools/profile)$', + '^(v8/tools/SourceMap)$', + '^(v8/tools/splaytree)$', + '^(v8/tools/tickprocessor-driver)$', + '^(v8/tools/tickprocessor)$', + '^(node-inspect/lib/_inspect)$', + '^(node-inspect/lib/internal/inspect_client)$', + '^(node-inspect/lib/internal/inspect_repl)$', + '^(async_hooks)$', + '^(punycode)$', + '^(domain)$', + '^(constants)$', + '^(sys)$', + '^(_linklist)$', + '^(_stream_wrap)$', + ], + }, + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['deprecated'], + }, + }, + // { + // name: 'no-non-package-json', + // severity: 'error', + // comment: + // "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + // "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + // 'available on live with an non-guaranteed version. Fix it by adding the package to the dependencies ' + + // 'in your package.json.', + // from: {}, + // to: { + // dependencyTypes: ['npm-no-pkg', 'npm-unknown'], + // }, + // }, + // { + // name: 'not-to-unresolvable', + // comment: + // "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + // 'module: add it to your package.json. In all other cases you likely already know what to do.', + // severity: 'error', + // from: {}, + // to: { + // couldNotResolve: true, + // }, + // }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + 'in your package.json i.e. bot as a devDependencies and in dependencies. This will cause ' + + 'maintenance problems later on.', + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ['type-only'], + }, + }, + + /* rules you might want to tweak for your specific situation: */ + + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '[.](spec|test)[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$', + }, + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(packages|libs)', + pathNot: + '[.](spec|test)[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$', + }, + to: { + dependencyTypes: ['npm-dev'], + // type only dependencies are not a problem as they don't end up in the + // production code or are ignored by the runtime. + dependencyTypesNot: ['type-only'], + pathNot: ['node_modules/@types/'], + }, + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + 'This module depends on an npm package that is declared as an optional dependency ' + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + 'dependency-cruiser configuration.', + from: {}, + to: { + dependencyTypes: ['npm-optional'], + }, + }, + { + name: 'peer-deps-used', + comment: + 'This module depends on an npm package that is declared as a peer dependency ' + + 'in your package.json. This makes sense if your package is e.g. a plugin, but in ' + + 'other cases - maybe not so much. If the use of a peer dependency is intentional ' + + 'add an exception to your dependency-cruiser configuration.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['npm-peer'], + }, + }, + ], + options: { + /* conditions specifying which files not to follow further when encountered: + - path: a regular expression to match + - dependencyTypes: see https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md#dependencytypes-and-dependencytypesnot + for a complete list + */ + doNotFollow: { + path: 'node_modules', + }, + + /* conditions specifying which dependencies to exclude + - path: a regular expression to match + - dynamic: a boolean indicating whether to ignore dynamic (true) or static (false) dependencies. + leave out if you want to exclude neither (recommended!) + */ + exclude: { + path: 'node_modules|fs|http|crypto|path|perf_hooks|v8', + }, + + /* pattern specifying which files to include (regular expression) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : '', + + /* dependency-cruiser will include modules matching against the focus + regular expression in its output, as well as their neighbours (direct + dependencies and dependents) + */ + // focus : '', + + /* List of module systems to cruise. + When left out dependency-cruiser will fall back to the list of _all_ + module systems it knows of. It's the default because it's the safe option + It might come at a performance penalty, though. + moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] + + As in practice only commonjs ('cjs') and ecmascript modules ('es6') + are widely used, you can limit the moduleSystems to those. + */ + + // moduleSystems: ['cjs', 'es6'], + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/develop/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: '', + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + tsPreCompilationDeps: true, + + /* + list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + tsConfig: { + fileName: 'tsconfig.json', + }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters to be passed if + your webpack config is a function and takes them (see webpack documentation + for details) + */ + // webpackConfig: { + // fileName: 'webpack.config.js', + // env: {}, + // arguments: {} + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation (and whatever other naughty things babel plugins do to + source code). This feature is well tested and usable, but might change + behavior a bit over time (e.g. more precise results for used module + systems) without dependency-cruiser getting a major version bump. + */ + // babelConfig: { + // fileName: '.babelrc', + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. You can set most of these + options in a webpack.conf.js - this section is here for those + projects that don't have a separate webpack config file. + + Note: settings in webpack.conf.js override the ones specified here. + */ + enhancedResolveOptions: { + /* List of strings to consider as 'exports' fields in package.json. Use + ['exports'] when you use packages that use such a field and your environment + supports it (e.g. node ^12.19 || >=14.7 or recent versions of webpack). + + If you have an `exportsFields` attribute in your webpack config, that one + will have precedence over the one specified here. + */ + exportsFields: ['exports'], + /* List of conditions to check for in the exports field. e.g. use ['imports'] + if you're only interested in exposed es6 modules, ['require'] for commonjs, + or all conditions at once `(['import', 'require', 'node', 'default']`) + if anything goes for you. Only works when the 'exportsFields' array is + non-empty. + + If you have a 'conditionNames' attribute in your webpack config, that one will + have precedence over the one specified here. + */ + conditionNames: ['import', 'require', 'node', 'default', 'types'], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment. If that list is larger than what you need (e.g. + it contains .js, .jsx, .ts, .tsx, .cts, .mts - but you don't use + TypeScript you can pass just the extensions you actually use (e.g. + [".js", ".jsx"]). This can speed up the most expensive step in + dependency cruising (module resolution) quite a bit. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* + If your TypeScript project makes use of types specified in 'types' + fields in package.jsons of external dependencies, specify "types" + in addition to "main" in here, so enhanced-resolve (the resolver + dependency-cruiser uses) knows to also look there. You can also do + this if you're not sure, but still use TypeScript. In a future version + of dependency-cruiser this will likely become the default. + */ + mainFields: ['main', 'types', 'typings'], + /* + A list of alias fields in manifests (package.jsons). + Specify a field, such as browser, to be parsed according to + [this specification](https://github.com/defunctzombie/package-browser-field-spec). + Also see [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) + in the webpack docs. + + Defaults to an empty array (don't use any alias fields). + */ + // aliasFields: ["browser"], + }, + reporterOptions: { + dot: { + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but not the innards your app depends upon. + */ + collapsePattern: 'node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + don't worry - dependency-cruiser will fall back to the default one. + */ + // theme: { + // graph: { + // /* use splines: "ortho" for straight lines. Be aware though + // graphviz might take a long time calculating ortho(gonal) + // routings. + // */ + // splines: "true" + // }, + // modules: [ + // { + // criteria: { matchesFocus: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesFocus: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { matchesReaches: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesReaches: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { source: "^src/model" }, + // attributes: { fillcolor: "#ccccff" } + // }, + // { + // criteria: { source: "^src/view" }, + // attributes: { fillcolor: "#ccffcc" } + // }, + // ], + // dependencies: [ + // { + // criteria: { "rules[0].severity": "error" }, + // attributes: { fontcolor: "red", color: "red" } + // }, + // { + // criteria: { "rules[0].severity": "warn" }, + // attributes: { fontcolor: "orange", color: "orange" } + // }, + // { + // criteria: { "rules[0].severity": "info" }, + // attributes: { fontcolor: "blue", color: "blue" } + // }, + // { + // criteria: { resolved: "^src/model" }, + // attributes: { color: "#0000ff77" } + // }, + // { + // criteria: { resolved: "^src/view" }, + // attributes: { color: "#00770077" } + // } + // ] + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: '^(packages/[^/]+|libs/[^/]+|node_modules)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + for 'archi' dependency-cruiser will use the one specified in the + dot section (see above), if any, and otherwise use the default one. + */ + theme: { + modules: [ + { + criteria: { collapsed: true }, + attributes: { shape: 'tab' }, + }, + ], + }, + }, + text: { + highlightFocused: true, + }, + }, + }, +}; +// generated: dependency-cruiser@16.2.0 on 2024-02-16T16:11:15.459Z diff --git a/.gitignore b/.gitignore index 7ff7e8445b5..33bbd3cee3e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ node_modules/ *.postgres *.rabbitmq redis-data -**/.nyc_output \ No newline at end of file +**/.nyc_output +**/dependency-graph.svg \ No newline at end of file diff --git a/package.json b/package.json index 92d7737bac7..1eeb94521a0 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "build-ci": "yarn global add node-gyp && yarn --ignore-engines && yarn build && yarn workspace commonwealth migrate-db", "chromatic": "chromatic --exit-zero-on-changes", "clean": "find . -name node_modules -exec rm -rf {} \\;; yarn --ignore-engines", + "dependency-graph": "npx depcruise packages/commonwealth/server.ts -T archi | dot -T svg > dependency-graph.svg", "dump-db": "yarn --cwd packages/commonwealth dump-db", "format": "prettier --write .", "format-check": "prettier --check .", @@ -111,6 +112,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^2.1.0", "css-minimizer-webpack-plugin": "^4.2.2", + "dependency-cruiser": "^16.2.0", "esbuild-loader": "^2.16.0", "eslint": "^8.51.0", "eslint-config-prettier": "^8.5.0", diff --git a/yarn.lock b/yarn.lock index f2da11b8eda..0ab370d57d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7092,11 +7092,28 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.3.2: +acorn-jsx-walk@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz#a5ed648264e68282d7c2aead80216bfdf232573a" + integrity sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA== + +acorn-jsx@5.3.2, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-loose@8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.4.0.tgz#26d3e219756d1e180d006f5bcc8d261a28530f55" + integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ== + dependencies: + acorn "^8.11.0" + +acorn-walk@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + acorn-walk@^8.0.0: version "8.0.2" resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.2.tgz" @@ -7107,6 +7124,11 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn@8.11.3, acorn@^8.11.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + acorn@^8.0.4: version "8.1.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz" @@ -7199,6 +7221,16 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" +ajv@8.12.0, ajv@^8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ajv@^6.1.0, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -7239,16 +7271,6 @@ ajv@^8.11.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -7353,6 +7375,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + any-promise@^1.1.0, any-promise@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -8582,6 +8609,11 @@ chai@^4.3.6: pathval "^1.1.1" type-detect "^4.0.8" +chalk@5.3.0, chalk@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -8623,11 +8655,6 @@ chalk@^5.0.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== -chalk@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - chance@^1.1.11: version "1.1.11" resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.11.tgz#78e10e1f9220a5bbc60a83e3f28a5d8558d84d1b" @@ -9006,6 +9033,11 @@ command-line-args@^4.0.7: find-replace "^1.0.3" typical "^2.6.1" +commander@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.0.0.tgz#b929db6df8546080adfd004ab215ed48cf6f2592" + integrity sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA== + commander@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" @@ -10025,6 +10057,38 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +dependency-cruiser@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/dependency-cruiser/-/dependency-cruiser-16.2.0.tgz#d4cbedaeeb30d8be03db2ce27fac662abfa10bf6" + integrity sha512-ud6xsI7h3T5zFhGjEYg7rtBizA9kKe+uR6ynuOLftnj55nCFp1g7kgbTX/zypCUkmZnkiEvFl9C/BC2kNVvSPQ== + dependencies: + acorn "8.11.3" + acorn-jsx "5.3.2" + acorn-jsx-walk "2.0.0" + acorn-loose "8.4.0" + acorn-walk "8.3.2" + ajv "8.12.0" + chalk "5.3.0" + commander "12.0.0" + enhanced-resolve "5.15.0" + figures "6.0.1" + ignore "5.3.1" + indent-string "5.0.0" + interpret "^3.1.1" + is-installed-globally "1.0.0" + json5 "2.2.3" + lodash "4.17.21" + picomatch "3.0.1" + prompts "2.4.2" + rechoir "^0.8.0" + safe-regex "2.1.1" + semver "^7.5.4" + semver-try-require "6.2.3" + teamcity-service-messages "0.1.14" + tsconfig-paths-webpack-plugin "4.1.0" + watskeburt "2.0.5" + wrap-ansi "9.0.0" + des.js@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz" @@ -10426,6 +10490,11 @@ elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emoji-regex@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" + integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz" @@ -10465,6 +10534,14 @@ end-of-stream@~1.1.0: dependencies: once "~1.3.0" +enhanced-resolve@5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + enhanced-resolve@^0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz" @@ -11860,6 +11937,13 @@ fbjs@^3.0.0, fbjs@^3.0.1: setimmediate "^1.0.5" ua-parser-js "^1.0.35" +figures@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/figures/-/figures-6.0.1.tgz#75a2baf3ca8c63e2ea253179e9f1157833587ea4" + integrity sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ== + dependencies: + is-unicode-supported "^2.0.0" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -12338,6 +12422,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-east-asian-width@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" + integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== + get-func-name@^2.0.1, get-func-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" @@ -12521,6 +12610,13 @@ glob@^7.1.1, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-directory@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e" + integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== + dependencies: + ini "4.1.1" + global-modules@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz" @@ -13275,6 +13371,11 @@ ignore-styles@^5.0.1: resolved "https://registry.yarnpkg.com/ignore-styles/-/ignore-styles-5.0.1.tgz#b49ef2274bdafcd8a4880a966bfe38d1a0bf4671" integrity sha512-gQQmIznCETPLEzfg1UH4Cs2oRq+HBPl8quroEUNXT8oybEG7/0lqI3dGgDSRry6B9HcCXw3PVkFFS0FF3CMddg== +ignore@5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -13334,16 +13435,16 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@5.0.0, indent-string@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" + integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== + indent-string@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indent-string@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" - integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== - indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -13377,6 +13478,11 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +ini@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" + integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== + ini@^1.3.4, ini@^1.3.5: version "1.3.5" resolved "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz" @@ -13438,6 +13544,11 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + invert-kv@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz" @@ -13760,6 +13871,14 @@ is-hex-prefixed@1.0.0: resolved "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz" integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= +is-installed-globally@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a" + integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== + dependencies: + global-directory "^4.0.1" + is-path-inside "^4.0.0" + is-interactive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" @@ -13851,6 +13970,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-path-inside@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db" + integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== + is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" @@ -14018,6 +14142,11 @@ is-unicode-supported@^1.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-unicode-supported@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz#fdf32df9ae98ff6ab2cedc155a5a6e895701c451" + integrity sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q== + is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz" @@ -14646,6 +14775,11 @@ json-to-graphql-query@^2.2.4: resolved "https://registry.yarnpkg.com/json-to-graphql-query/-/json-to-graphql-query-2.2.4.tgz#ada9cfdbb9bf38589fd2661e1588d1edd0a882cc" integrity sha512-vNvsOKDSlEqYCzejI1xHS9Hm738dSnG4Upy09LUGqyybZXSIIb7NydDphB/6WxW2EEVpPU4JeU/Yo63Nw9dEJg== +json5@2.2.3, json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + json5@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -14658,11 +14792,6 @@ json5@^2.1.2, json5@^2.2.0: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== -json5@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" @@ -14845,6 +14974,11 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + kleur@^4.1.4: version "4.1.5" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" @@ -15329,7 +15463,7 @@ lodash.values@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" integrity sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q== -lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5: +lodash@4.17.21, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -17477,6 +17611,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picomatch@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" + integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== + picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.3" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz" @@ -18370,6 +18509,14 @@ promiseback@^2.0.2: is-callable "^1.1.5" promise-deferred "^2.0.3" +prompts@2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -19149,6 +19296,13 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + redent@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" @@ -19241,6 +19395,11 @@ regenerator-transform@^0.15.1: dependencies: "@babel/runtime" "^7.8.4" +regexp-tree@~0.1.1: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz" @@ -19665,6 +19824,13 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +safe-regex@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" + integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== + dependencies: + regexp-tree "~0.1.1" + safe-require@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-require/-/safe-require-1.0.4.tgz#c72e1fb2820e31080e5f26fdde252a1cbffe08ad" @@ -19806,6 +19972,13 @@ secp256k1@^4.0.1, secp256k1@^4.0.2: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +semver-try-require@6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/semver-try-require/-/semver-try-require-6.2.3.tgz#aefe418635fa4604a063749cab19b346494409e4" + integrity sha512-6q1N/Vr/4/G0EcQ1k4svN5kwfh3MJs4Gfl+zBAVcKn+AeIjKLwTXQ143Y6YHu6xEeN5gSCbCD1/5+NwCipLY5A== + dependencies: + semver "^7.5.3" + "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" @@ -19847,6 +20020,13 @@ semver@^7.5.1, semver@^7.5.4: dependencies: lru-cache "^6.0.0" +semver@^7.5.3: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz" @@ -20179,6 +20359,11 @@ sirv@^2.0.2: mrmime "^1.0.0" totalist "^3.0.0" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + siwe@^2.1.3, siwe@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/siwe/-/siwe-2.1.4.tgz#005a8be3e61224a86bd3457f60fdaab626f2d1d4" @@ -20606,6 +20791,15 @@ string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a" + integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + string.prototype.matchall@^4.0.8: version "4.0.10" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" @@ -20768,6 +20962,13 @@ strip-ansi@^7.0.0, strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" @@ -21207,6 +21408,11 @@ tdigest@^0.1.1: dependencies: bintrees "1.0.2" +teamcity-service-messages@0.1.14: + version "0.1.14" + resolved "https://registry.yarnpkg.com/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz#193d420a5e4aef8e5e50b8c39e7865e08fbb5d8a" + integrity sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w== + temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -21589,7 +21795,7 @@ tsc-alias@^1.8.8: normalize-path "^3.0.0" plimit-lit "^1.2.6" -tsconfig-paths-webpack-plugin@^4.1.0: +tsconfig-paths-webpack-plugin@4.1.0, tsconfig-paths-webpack-plugin@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz#3c6892c5e7319c146eee1e7302ed9e6f2be4f763" integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== @@ -22472,6 +22678,11 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +watskeburt@2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/watskeburt/-/watskeburt-2.0.5.tgz#1e9e861927f02e67a011d9e8a250feacd8ab8d01" + integrity sha512-mCxVT4o/U+nNxmYJ+Oei3qre4F28IBWR9c7RGS8aeAR7a7UY1y4ikTWcv+kaaybf1QXUANmB7XBHawHBcmg+kw== + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" @@ -23105,6 +23316,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +wrap-ansi@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz" From a0ee024ffd2727897bc19830a64ce4e798cb8316 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 14:32:06 -0500 Subject: [PATCH 11/17] fix logging --- libs/core/src/ports/in-memory-logger.ts | 46 +++++++++ libs/core/src/ports/index.ts | 94 ------------------ libs/core/src/ports/port.ts | 97 +++++++++++++++++-- .../src/community/CreateGroup.command.ts | 3 +- libs/model/src/test/seedDb.ts | 4 +- 5 files changed, 140 insertions(+), 104 deletions(-) create mode 100644 libs/core/src/ports/in-memory-logger.ts diff --git a/libs/core/src/ports/in-memory-logger.ts b/libs/core/src/ports/in-memory-logger.ts new file mode 100644 index 00000000000..033fb878e69 --- /dev/null +++ b/libs/core/src/ports/in-memory-logger.ts @@ -0,0 +1,46 @@ +import { Logger } from './interfaces'; + +const devLogger: Logger = { + name: 'in-memory-logger', + dispose: () => Promise.resolve(), + getLogger: () => ({ + trace(msg: string) { + console.log(msg); + }, + debug(msg: string) { + console.log(msg); + }, + info(msg: string) { + console.log(msg); + }, + warn(msg: string) { + console.log(msg); + }, + error(msg: string, error?: Error) { + console.error(msg, error?.message); + }, + fatal(msg: string, error?: Error) { + console.error(msg, error?.message); + }, + }), +}; + +const testLogger: Logger = { + name: 'in-memory-logger', + dispose: () => Promise.resolve(), + getLogger: () => ({ + trace() {}, + debug() {}, + info() {}, + warn() {}, + error(msg: string, error?: Error) { + console.error(msg, error?.message); + }, + fatal(msg: string, error?: Error) { + console.error(msg, error?.message); + }, + }), +}; + +export const getInMemoryLogger = () => + process.env.NODE_ENV === 'test' ? testLogger : devLogger; diff --git a/libs/core/src/ports/index.ts b/libs/core/src/ports/index.ts index 504087b1a48..a4dd3093a03 100644 --- a/libs/core/src/ports/index.ts +++ b/libs/core/src/ports/index.ts @@ -1,97 +1,3 @@ -import { Analytics, Cache, Logger, Stats } from './interfaces'; -import { port } from './port'; - export * from './enums'; export * from './interfaces'; export * from './port'; - -/** - * Logger port factory - */ -export const logger = port(function logger(logger?: Logger) { - return ( - logger || { - name: 'in-memory-logger', - dispose: () => Promise.resolve(), - getLogger: () => ({ - trace(msg: string) { - console.log(msg); - }, - debug(msg: string) { - console.log(msg); - }, - info(msg: string) { - console.log(msg); - }, - warn(msg: string) { - console.log(msg); - }, - error(msg: string, error?: Error) { - console.error(msg, error?.message); - }, - fatal(msg: string, error?: Error) { - console.error(msg, error?.message); - }, - }), - } - ); -}); - -/** - * Stats port factory - */ -export const stats = port(function stats(stats?: Stats) { - return ( - stats || { - name: 'in-memory-stats', - dispose: () => Promise.resolve(), - histogram: () => {}, - set: () => {}, - increment: () => {}, - incrementBy: () => {}, - decrement: () => {}, - decrementBy: () => {}, - on: () => {}, - off: () => {}, - timing: () => {}, - } - ); -}); - -/** - * Cache port factory - */ -export const cache = port(function cache(cache?: Cache) { - return ( - cache || { - name: 'in-memory-cache', - dispose: () => Promise.resolve(), - getKey: () => Promise.resolve(''), - setKey: () => Promise.resolve(false), - getKeys: () => Promise.resolve(false), - setKeys: () => Promise.resolve(false), - getNamespaceKeys: () => Promise.resolve(false), - deleteKey: () => Promise.resolve(0), - deleteNamespaceKeys: () => Promise.resolve(0), - flushAll: () => Promise.resolve(), - incrementKey: () => Promise.resolve(0), - decrementKey: () => Promise.resolve(0), - getKeyTTL: () => Promise.resolve(0), - setKeyTTL: () => Promise.resolve(false), - } - ); -}); - -/** - * Analytics port factory - */ - -export const analytics = port(function analytics(analytics?: Analytics) { - return ( - analytics || { - name: 'in-memory-analytics', - dispose: () => Promise.resolve(), - track: () => {}, - } - ); -}); diff --git a/libs/core/src/ports/port.ts b/libs/core/src/ports/port.ts index 05c6b09e796..d85cb223034 100644 --- a/libs/core/src/ports/port.ts +++ b/libs/core/src/ports/port.ts @@ -1,5 +1,14 @@ import { ExitCode } from './enums'; -import { AdapterFactory, Disposable, Disposer } from './interfaces'; +import { getInMemoryLogger } from './in-memory-logger'; +import { + AdapterFactory, + Analytics, + Cache, + Disposable, + Disposer, + Logger, + Stats, +} from './interfaces'; /** * Map of disposable adapter instances @@ -16,7 +25,9 @@ export function port(factory: AdapterFactory) { if (!adapters.has(factory.name)) { const instance = factory(adapter); adapters.set(factory.name, instance); - console.log('[binding adapter]', instance.name || factory.name); + logger() + .getLogger('ports') + .info(`[binding adapter] ${instance.name || factory.name}`); return instance; } return adapters.get(factory.name) as T; @@ -36,7 +47,9 @@ const disposeAndExit = async (code: ExitCode = 'UNIT_TEST'): Promise => { await Promise.all(disposers.map((disposer) => disposer())); await Promise.all( [...adapters].map(async ([key, adapter]) => { - console.log('[disposing adapter]', adapter.name || key); + logger() + .getLogger('ports') + .info(`[disposing adapter] ${adapter.name || key}`); await adapter.dispose(); }), ); @@ -60,18 +73,88 @@ export const dispose = ( * Handlers to dispose registered resources on exit or unhandled exceptions */ process.once('SIGINT', async (arg?: any) => { - console.log('SIGINT', arg !== 'SIGINT' ? arg : ''); + logger() + .getLogger('ports') + .info(`SIGINT ${arg !== 'SIGINT' ? arg : ''}`); await disposeAndExit('EXIT'); }); process.once('SIGTERM', async (arg?: any) => { - console.log('SIGTERM', arg !== 'SIGTERM' ? arg : ''); + logger() + .getLogger('ports') + .info(`SIGTERM ${arg !== 'SIGTERM' ? arg : ''}`); await disposeAndExit('EXIT'); }); process.once('uncaughtException', async (arg?: any) => { - console.error('Uncaught Exception', arg); + logger().getLogger('ports').error('Uncaught Exception', arg); await disposeAndExit('ERROR'); }); process.once('unhandledRejection', async (arg?: any) => { - console.error('Unhandled Rejection', arg); + logger().getLogger('ports').error('Unhandled Rejection', arg); await disposeAndExit('ERROR'); }); + +/** + * Logger port factory + */ +export const logger = port(function logger(logger?: Logger) { + return logger || getInMemoryLogger(); +}); + +/** + * Stats port factory + */ +export const stats = port(function stats(stats?: Stats) { + return ( + stats || { + name: 'in-memory-stats', + dispose: () => Promise.resolve(), + histogram: () => {}, + set: () => {}, + increment: () => {}, + incrementBy: () => {}, + decrement: () => {}, + decrementBy: () => {}, + on: () => {}, + off: () => {}, + timing: () => {}, + } + ); +}); + +/** + * Cache port factory + */ +export const cache = port(function cache(cache?: Cache) { + return ( + cache || { + name: 'in-memory-cache', + dispose: () => Promise.resolve(), + getKey: () => Promise.resolve(''), + setKey: () => Promise.resolve(false), + getKeys: () => Promise.resolve(false), + setKeys: () => Promise.resolve(false), + getNamespaceKeys: () => Promise.resolve(false), + deleteKey: () => Promise.resolve(0), + deleteNamespaceKeys: () => Promise.resolve(0), + flushAll: () => Promise.resolve(), + incrementKey: () => Promise.resolve(0), + decrementKey: () => Promise.resolve(0), + getKeyTTL: () => Promise.resolve(0), + setKeyTTL: () => Promise.resolve(false), + } + ); +}); + +/** + * Analytics port factory + */ + +export const analytics = port(function analytics(analytics?: Analytics) { + return ( + analytics || { + name: 'in-memory-analytics', + dispose: () => Promise.resolve(), + track: () => {}, + } + ); +}); diff --git a/libs/model/src/community/CreateGroup.command.ts b/libs/model/src/community/CreateGroup.command.ts index 52670e30574..bc9c3006fbf 100644 --- a/libs/model/src/community/CreateGroup.command.ts +++ b/libs/model/src/community/CreateGroup.command.ts @@ -22,7 +22,6 @@ export const CreateGroup: CommandMetadata = schema, auth: [isCommunityAdminOrModerator], body: async ({ payload }) => { - console.log(payload); - return {}; + return payload; }, }; diff --git a/libs/model/src/test/seedDb.ts b/libs/model/src/test/seedDb.ts index 7dd731acc3b..9194536b062 100644 --- a/libs/model/src/test/seedDb.ts +++ b/libs/model/src/test/seedDb.ts @@ -12,7 +12,9 @@ import type { ChainNodeAttributes } from '../models/chain_node'; const checkDb = async () => { let sequelize: Sequelize | undefined = undefined; try { - sequelize = new Sequelize('postgresql://commonwealth:edgeware@localhost'); + sequelize = new Sequelize('postgresql://commonwealth:edgeware@localhost', { + logging: false, + }); const testdbname = 'common_test'; const [{ count }] = await sequelize.query<{ count: number }>( `SELECT COUNT(*) FROM pg_database WHERE datname = '${testdbname}'`, From 9cddc85d501fdfaa8f3caed8d16ab2e4d0cf511c Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 14:36:53 -0500 Subject: [PATCH 12/17] rename mixpanel adapter --- libs/adapters/src/mixpanel/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/adapters/src/mixpanel/index.ts b/libs/adapters/src/mixpanel/index.ts index 9dfbce1ebfc..8791428aa33 100644 --- a/libs/adapters/src/mixpanel/index.ts +++ b/libs/adapters/src/mixpanel/index.ts @@ -19,7 +19,7 @@ export const MixpanelAnalytics = (): Analytics => { } return { - name: 'mixpanel-analytics', + name: 'MixpanelAnalytics', dispose: async () => {}, track: (event: string, payload: AnalyticsOptions) => { try { From 301348a066876c2710bab09f3c5c1a5e2879da6a Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 15:03:09 -0500 Subject: [PATCH 13/17] add req to analytics middleware --- libs/adapters/src/express/middleware.ts | 12 ++++++------ packages/commonwealth/server/routes/ddd/community.ts | 11 ++++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libs/adapters/src/express/middleware.ts b/libs/adapters/src/express/middleware.ts index bc0481aaccd..3b8f275f3cc 100644 --- a/libs/adapters/src/express/middleware.ts +++ b/libs/adapters/src/express/middleware.ts @@ -70,17 +70,17 @@ export const errorMiddleware = ( * Captures analytics */ export function analyticsMiddleware( - event: any, - transformer: (payload: T) => AnalyticsOptions, + event: string, + transformer: (req: Request<{ id: string }>, results?: T) => AnalyticsOptions, ) { - return (req: Request, res: Response, next: NextFunction) => { + return (req: Request<{ id: string }>, res: Response, next: NextFunction) => { try { // override res.json const originalResJson = res.json; - res.json = function (data: T) { - analytics().track(event, transformer(data)); - return originalResJson.call(res, data); + res.json = function (results: T) { + analytics().track(event, transformer(req, results)); + return originalResJson.call(res, results); }; } catch (err: unknown) { console.error(err); // don't use logger port here diff --git a/packages/commonwealth/server/routes/ddd/community.ts b/packages/commonwealth/server/routes/ddd/community.ts index 07539b84bd4..42cdd818fe2 100644 --- a/packages/commonwealth/server/routes/ddd/community.ts +++ b/packages/commonwealth/server/routes/ddd/community.ts @@ -3,9 +3,10 @@ import { expressCommand, expressQuery, } from '@hicommonwealth/adapters'; -import { Community } from '@hicommonwealth/model'; +import { Community, CommunityAttributes } from '@hicommonwealth/model'; import { Router } from 'express'; import passport from 'passport'; +import { MixpanelCommunityInteractionEvent } from 'shared/analytics/types'; const router = Router(); @@ -24,14 +25,18 @@ router.put( router.post( '/:id/group', passport.authenticate('jwt', { session: false }), + analyticsMiddleware( + MixpanelCommunityInteractionEvent.CREATE_GROUP, + (req, results) => ({ community: results.id, user: req.user.id }), + ), expressCommand(Community.CreateGroup), ); router.post( '/demo', - analyticsMiddleware('Demo Event', (payload) => { + analyticsMiddleware('Demo Event', (_, results) => { return { - x: payload.numItems, + x: results.numItems, }; }), expressCommand(Community.Demo), From 1aaafe5d0809a57b676688a51f161da0997739dc Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 15:57:12 -0500 Subject: [PATCH 14/17] fix middleware --- libs/adapters/src/express/middleware.ts | 9 +++++++-- packages/commonwealth/server/routes/ddd/community.ts | 9 +++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/libs/adapters/src/express/middleware.ts b/libs/adapters/src/express/middleware.ts index 3b8f275f3cc..ff81015dd5d 100644 --- a/libs/adapters/src/express/middleware.ts +++ b/libs/adapters/src/express/middleware.ts @@ -2,6 +2,7 @@ import { AnalyticsOptions, INVALID_ACTOR_ERROR, INVALID_INPUT_ERROR, + User, analytics, stats, } from '@hicommonwealth/core'; @@ -71,7 +72,7 @@ export const errorMiddleware = ( */ export function analyticsMiddleware( event: string, - transformer: (req: Request<{ id: string }>, results?: T) => AnalyticsOptions, + transformer?: (results?: T) => AnalyticsOptions, ) { return (req: Request<{ id: string }>, res: Response, next: NextFunction) => { try { @@ -79,7 +80,11 @@ export function analyticsMiddleware( const originalResJson = res.json; res.json = function (results: T) { - analytics().track(event, transformer(req, results)); + analytics().track(event, { + userId: (req.user as User).id, + aggregateId: req.params.id, + ...(transformer ? transformer(results) : {}), + }); return originalResJson.call(res, results); }; } catch (err: unknown) { diff --git a/packages/commonwealth/server/routes/ddd/community.ts b/packages/commonwealth/server/routes/ddd/community.ts index 42cdd818fe2..a9fd1521d41 100644 --- a/packages/commonwealth/server/routes/ddd/community.ts +++ b/packages/commonwealth/server/routes/ddd/community.ts @@ -3,7 +3,7 @@ import { expressCommand, expressQuery, } from '@hicommonwealth/adapters'; -import { Community, CommunityAttributes } from '@hicommonwealth/model'; +import { Community } from '@hicommonwealth/model'; import { Router } from 'express'; import passport from 'passport'; import { MixpanelCommunityInteractionEvent } from 'shared/analytics/types'; @@ -25,16 +25,13 @@ router.put( router.post( '/:id/group', passport.authenticate('jwt', { session: false }), - analyticsMiddleware( - MixpanelCommunityInteractionEvent.CREATE_GROUP, - (req, results) => ({ community: results.id, user: req.user.id }), - ), + analyticsMiddleware(MixpanelCommunityInteractionEvent.CREATE_GROUP), expressCommand(Community.CreateGroup), ); router.post( '/demo', - analyticsMiddleware('Demo Event', (_, results) => { + analyticsMiddleware('Demo Event', (results) => { return { x: results.numItems, }; From 996daf311a9c678527374a329a954b8103f843cb Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Fri, 16 Feb 2024 17:40:37 -0500 Subject: [PATCH 15/17] implement create group --- .../community/group-lifecycle.spec.ts | 23 ++++-- .../src/community/CreateGroup.command.ts | 77 +++++++++++++++++-- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts index ac596f2efc0..72cdd91b822 100644 --- a/libs/model/__tests__/community/group-lifecycle.spec.ts +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -2,7 +2,11 @@ import { InvalidState, command, dispose } from '@hicommonwealth/core'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { Chance } from 'chance'; -import { CreateGroup, MAX_GROUPS_PER_COMMUNITY } from '../../src/community'; +import { + CreateGroup, + Errors, + MAX_GROUPS_PER_COMMUNITY, +} from '../../src/community/CreateGroup.command'; import { seedDb } from '../../src/test'; chai.use(chaiAsPromised); @@ -37,20 +41,21 @@ describe('Group lifecycle', () => { it('should create group when none exists', async () => { const results = await command(CreateGroup, context); - expect(results).to.deep.contain({ - groups: { ...context.payload.metadata }, - }); + expect(results?.groups?.at(0)?.metadata).to.includes( + context.payload.metadata, + ); }); it('should fail creation when group with same id found', () => { expect(command(CreateGroup, context)).to.eventually.be.rejectedWith( InvalidState, + Errors.GroupAlreadyExists, ); }); it('should fail creation when community reached max number of groups allowed', async () => { // create max groups - for (let i = 0; i < MAX_GROUPS_PER_COMMUNITY; i++) + for (let i = 1; i < MAX_GROUPS_PER_COMMUNITY; i++) { await command(CreateGroup, { ...context, payload: { @@ -58,6 +63,7 @@ describe('Group lifecycle', () => { metadata: { name: chance.name(), description: chance.sentence() }, }, }); + } const invalid = { ...context, @@ -74,10 +80,12 @@ describe('Group lifecycle', () => { }; expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( InvalidState, + Errors.MaxGroups, ); }); it('should fail creation when sending invalid topics', () => { + console.log('testing'); const invalid = { ...context, payload: { @@ -87,12 +95,13 @@ describe('Group lifecycle', () => { required_requirements: 1, membership_ttl: 100, }, - requirements: [], // TODO: - topics: [], // TODO: set invalid topic here + requirements: [], + topics: [1, 2, 3], }, }; expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( InvalidState, + Errors.InvalidTopics, ); }); }); diff --git a/libs/model/src/community/CreateGroup.command.ts b/libs/model/src/community/CreateGroup.command.ts index bc9c3006fbf..4e9ea18029f 100644 --- a/libs/model/src/community/CreateGroup.command.ts +++ b/libs/model/src/community/CreateGroup.command.ts @@ -1,7 +1,9 @@ -import { CommandMetadata } from '@hicommonwealth/core'; +import { CommandMetadata, InvalidState } from '@hicommonwealth/core'; +import { Op } from 'sequelize'; import { z } from 'zod'; +import { models, sequelize } from '../database'; import { isCommunityAdminOrModerator } from '../middleware'; -import { CommunityAttributes } from '../models'; +import { CommunityAttributes, GroupAttributes } from '../models'; import { Requirement } from './Requirements.schema'; const schema = z.object({ @@ -16,12 +18,77 @@ const schema = z.object({ }); export const MAX_GROUPS_PER_COMMUNITY = 20; +export const Errors = { + MaxGroups: 'Exceeded max number of groups', + InvalidTopics: 'Invalid topics', + GroupAlreadyExists: 'Group already exists', +}; export const CreateGroup: CommandMetadata = { schema, - auth: [isCommunityAdminOrModerator], - body: async ({ payload }) => { - return payload; + auth: [isCommunityAdminOrModerator], // TODO: create reusable middleware to authorize owner, admins, moderators + body: async ({ id, payload }) => { + const groups = await models.Group.findAll({ + where: { + community_id: id, + }, // TODO: just return the names + }); + + if (groups.find((g) => g.metadata.name === payload.metadata.name)) + throw new InvalidState(Errors.GroupAlreadyExists); + + if (groups.length >= MAX_GROUPS_PER_COMMUNITY) + throw new InvalidState(Errors.MaxGroups); + + const topicsToAssociate = await models.Topic.findAll({ + where: { + id: { + [Op.in]: payload.topics || [], + }, + community_id: id, + }, + }); + if (payload.topics?.length !== topicsToAssociate.length) + throw new InvalidState(Errors.InvalidTopics); + + const newGroup = await models.sequelize.transaction( + async (transaction) => { + // create group + const group = await models.Group.create( + { + community_id: id!, + metadata: payload.metadata, + requirements: payload.requirements, + } as GroupAttributes, + { transaction }, + ); + if (topicsToAssociate.length > 0) { + // add group to all specified topics + await models.Topic.update( + { + group_ids: sequelize.fn( + 'array_append', + sequelize.col('group_ids'), + group.id, + ), + }, + { + where: { + id: { + [Op.in]: topicsToAssociate.map(({ id }) => id!), + }, + }, + transaction, + }, + ); + } + return group.toJSON(); + }, + ); + + // TODO: refresh memberships async + + return { id, groups: [newGroup] }; }, }; From ecf40be910db8a683d8e6ae6d912b3cb268d70bd Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Sat, 17 Feb 2024 10:55:31 -0500 Subject: [PATCH 16/17] fix requirement schema --- .../community/group-lifecycle.spec.ts | 11 +++---- ...-stake.spec.ts => stake-lifecycle.spec.ts} | 2 +- .../src/community/CreateGroup.command.ts | 16 ++++++---- .../src/community/Requirements.schema.ts | 29 ++++++++++++------- 4 files changed, 35 insertions(+), 23 deletions(-) rename libs/model/__tests__/community/{community-stake.spec.ts => stake-lifecycle.spec.ts} (98%) diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts index 72cdd91b822..0b4b23e68db 100644 --- a/libs/model/__tests__/community/group-lifecycle.spec.ts +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -26,8 +26,8 @@ describe('Group lifecycle', () => { required_requirements: 1, membership_ttl: 100, }, - requirements: [], // TODO: - topics: [], // TODO: + requirements: [], + topics: [], }, }; @@ -71,11 +71,9 @@ describe('Group lifecycle', () => { metadata: { name: chance.name(), description: chance.sentence(), - required_requirements: 1, - membership_ttl: 100, }, - requirements: [], // TODO: - topics: [], // TODO: + requirements: [], + topics: [], }, }; expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( @@ -93,7 +91,6 @@ describe('Group lifecycle', () => { name: chance.name(), description: chance.sentence(), required_requirements: 1, - membership_ttl: 100, }, requirements: [], topics: [1, 2, 3], diff --git a/libs/model/__tests__/community/community-stake.spec.ts b/libs/model/__tests__/community/stake-lifecycle.spec.ts similarity index 98% rename from libs/model/__tests__/community/community-stake.spec.ts rename to libs/model/__tests__/community/stake-lifecycle.spec.ts index 4fdeff1518d..27bb7af2951 100644 --- a/libs/model/__tests__/community/community-stake.spec.ts +++ b/libs/model/__tests__/community/stake-lifecycle.spec.ts @@ -13,7 +13,7 @@ import { seedDb } from '../../src/test'; chai.use(chaiAsPromised); -describe('Community stake', () => { +describe('Stake lifecycle', () => { const context = { id: 'common-protocol', actor: { diff --git a/libs/model/src/community/CreateGroup.command.ts b/libs/model/src/community/CreateGroup.command.ts index 4e9ea18029f..265b4d10ac3 100644 --- a/libs/model/src/community/CreateGroup.command.ts +++ b/libs/model/src/community/CreateGroup.command.ts @@ -27,12 +27,12 @@ export const Errors = { export const CreateGroup: CommandMetadata = { schema, - auth: [isCommunityAdminOrModerator], // TODO: create reusable middleware to authorize owner, admins, moderators + auth: [isCommunityAdminOrModerator], body: async ({ id, payload }) => { const groups = await models.Group.findAll({ - where: { - community_id: id, - }, // TODO: just return the names + where: { community_id: id }, + attributes: ['metadata'], + raw: true, }); if (groups.find((g) => g.metadata.name === payload.metadata.name)) @@ -87,7 +87,13 @@ export const CreateGroup: CommandMetadata = }, ); - // TODO: refresh memberships async + // TODO: create domain service to refresh community memberships + // TODO: create integration policy to connect creation events (like groups) to service above + // TODO: creation integration test that validates this refresh flow + //.refreshCommunityMemberships({ + // communityId: id, + // groupId: newGroup.id, + // }) return { id, groups: [newGroup] }; }, diff --git a/libs/model/src/community/Requirements.schema.ts b/libs/model/src/community/Requirements.schema.ts index 83f29d7902f..fcda2548411 100644 --- a/libs/model/src/community/Requirements.schema.ts +++ b/libs/model/src/community/Requirements.schema.ts @@ -1,7 +1,12 @@ +import { BalanceSourceType } from '@hicommonwealth/core'; import { z } from 'zod'; const ContractSource = z.object({ - source_type: z.enum(['erc20', 'erc721', 'erc1155']), + source_type: z.enum([ + BalanceSourceType.ERC20, + BalanceSourceType.ERC721, + BalanceSourceType.ERC1155, + ]), evm_chain_id: z.number(), contract_address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), token_id: z @@ -11,18 +16,18 @@ const ContractSource = z.object({ }); const NativeSource = z.object({ - source_type: z.enum(['eth_native']), + source_type: z.enum([BalanceSourceType.ETHNative]), evm_chain_id: z.number(), }); const CosmosSource = z.object({ - source_type: z.enum(['cosmos_native']), + source_type: z.enum([BalanceSourceType.CosmosNative]), cosmos_chain_id: z.string(), token_symbol: z.string(), }); const CosmosContractSource = z.object({ - source_type: z.enum(['cw721']), + source_type: z.enum([BalanceSourceType.CW721]), cosmos_chain_id: z.string(), contract_address: z.string(), }); @@ -41,9 +46,13 @@ const AllowlistData = z.object({ allow: z.array(z.string().regex(/^0x[a-fA-F0-9]{40}$/)), }); -const RuleData = z.union([ThresholdData, AllowlistData]); - -export const Requirement = z.object({ - rule: z.enum(['threshold', 'allow']), - data: RuleData, -}); +export const Requirement = z.union([ + z.object({ + rule: z.enum(['threshold']), + data: ThresholdData, + }), + z.object({ + rule: z.enum(['allow']), + data: AllowlistData, + }), +]); From 400ee8725a3d02da91131a765fe124c088be9f4c Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Sat, 17 Feb 2024 11:56:26 -0500 Subject: [PATCH 17/17] make commands and queries as factories --- libs/core/src/framework/types.ts | 12 +- .../community/group-lifecycle.spec.ts | 47 +++--- .../community/stake-lifecycle.spec.ts | 12 +- .../src/comment/CreateComment.command.ts | 24 ++-- .../src/community/CreateCommunity.command.ts | 6 +- .../src/community/CreateGroup.command.ts | 136 +++++++++--------- libs/model/src/community/Demo.command.ts | 18 --- .../src/community/GetCommunityStake.query.ts | 6 +- .../community/SetCommunityStake.command.ts | 6 +- .../src/community/UpdateCommunity.command.ts | 6 +- libs/model/src/community/index.ts | 1 - .../src/reaction/CreateReaction.command.ts | 6 +- libs/model/src/thread/CreateThread.command.ts | 7 +- libs/model/src/user/CreateUser.command.ts | 7 +- .../communities/create_community_handler.ts | 2 +- .../server/routes/ddd/community.ts | 16 +-- 16 files changed, 146 insertions(+), 166 deletions(-) delete mode 100644 libs/model/src/community/Demo.command.ts diff --git a/libs/core/src/framework/types.ts b/libs/core/src/framework/types.ts index 1973eff0f28..4ab830489bb 100644 --- a/libs/core/src/framework/types.ts +++ b/libs/core/src/framework/types.ts @@ -123,9 +123,9 @@ export type QueryHandler = ( * - `body`: function implementing core domain logic, and returning side effects (mutations) */ export type CommandMetadata = { - schema: P; - auth: CommandHandler[]; - body: CommandHandler; + readonly schema: P; + readonly auth: CommandHandler[]; + readonly body: CommandHandler; }; /** @@ -135,7 +135,7 @@ export type CommandMetadata = { * - `body`: function implementing the query logic */ export type QueryMetadata = { - schema: P; - auth: QueryHandler[]; - body: QueryHandler; + readonly schema: P; + readonly auth: QueryHandler[]; + readonly body: QueryHandler; }; diff --git a/libs/model/__tests__/community/group-lifecycle.spec.ts b/libs/model/__tests__/community/group-lifecycle.spec.ts index 0b4b23e68db..4a458edaff4 100644 --- a/libs/model/__tests__/community/group-lifecycle.spec.ts +++ b/libs/model/__tests__/community/group-lifecycle.spec.ts @@ -40,65 +40,64 @@ describe('Group lifecycle', () => { }); it('should create group when none exists', async () => { - const results = await command(CreateGroup, context); + const results = await command(CreateGroup(), context); expect(results?.groups?.at(0)?.metadata).to.includes( context.payload.metadata, ); }); it('should fail creation when group with same id found', () => { - expect(command(CreateGroup, context)).to.eventually.be.rejectedWith( + expect(command(CreateGroup(), context)).to.eventually.be.rejectedWith( InvalidState, - Errors.GroupAlreadyExists, ); }); - it('should fail creation when community reached max number of groups allowed', async () => { - // create max groups - for (let i = 1; i < MAX_GROUPS_PER_COMMUNITY; i++) { - await command(CreateGroup, { - ...context, - payload: { - ...context.payload, - metadata: { name: chance.name(), description: chance.sentence() }, - }, - }); - } - + it('should fail creation when sending invalid topics', () => { + console.log('testing'); const invalid = { ...context, payload: { metadata: { name: chance.name(), description: chance.sentence(), + required_requirements: 1, }, requirements: [], - topics: [], + topics: [1, 2, 3], }, }; - expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( + expect(command(CreateGroup(), invalid)).to.eventually.be.rejectedWith( InvalidState, - Errors.MaxGroups, + Errors.InvalidTopics, ); }); - it('should fail creation when sending invalid topics', () => { - console.log('testing'); + it('should fail creation when community reached max number of groups allowed', async () => { + // create max groups + for (let i = 1; i < MAX_GROUPS_PER_COMMUNITY; i++) { + await command(CreateGroup(), { + ...context, + payload: { + ...context.payload, + metadata: { name: chance.name(), description: chance.sentence() }, + }, + }); + } + const invalid = { ...context, payload: { metadata: { name: chance.name(), description: chance.sentence(), - required_requirements: 1, }, requirements: [], - topics: [1, 2, 3], + topics: [], }, }; - expect(command(CreateGroup, invalid)).to.eventually.be.rejectedWith( + expect(command(CreateGroup(), invalid)).to.eventually.be.rejectedWith( InvalidState, - Errors.InvalidTopics, + Errors.MaxGroups, ); }); }); diff --git a/libs/model/__tests__/community/stake-lifecycle.spec.ts b/libs/model/__tests__/community/stake-lifecycle.spec.ts index 27bb7af2951..544c9a93779 100644 --- a/libs/model/__tests__/community/stake-lifecycle.spec.ts +++ b/libs/model/__tests__/community/stake-lifecycle.spec.ts @@ -38,7 +38,7 @@ describe('Stake lifecycle', () => { it('should fail set when community namespace not configured', () => { _validateCommunityStakeConfig.onFirstCall().rejects(); - expect(command(SetCommunityStake, context)).to.eventually.be.rejected; + expect(command(SetCommunityStake(), context)).to.eventually.be.rejected; }); it('should set and get community stake', async () => { @@ -51,7 +51,7 @@ describe('Stake lifecycle', () => { url: 'https://ethereum-sepolia.publicnode.com', }; - const cr = await command(SetCommunityStake, context); + const cr = await command(SetCommunityStake(), context); expect(cr).to.deep.contains({ ...community.Chain, ChainNode, @@ -65,7 +65,7 @@ describe('Stake lifecycle', () => { ], }); - const qr = await query(GetCommunityStake, { + const qr = await query(GetCommunityStake(), { actor: context.actor, payload: { community_id: context.id }, }); @@ -74,13 +74,13 @@ describe('Stake lifecycle', () => { it('should fail set when community not found', async () => { expect( - command(SetCommunityStake, { ...context, id: 'does-not-exist' }), + command(SetCommunityStake(), { ...context, id: 'does-not-exist' }), ).to.eventually.be.rejectedWith(InvalidActor); }); it('should fail set when community stake has been configured', () => { expect( - command(SetCommunityStake, { + command(SetCommunityStake(), { ...context, actor: { user: { id: 2, email: '' }, @@ -95,7 +95,7 @@ describe('Stake lifecycle', () => { }); it('should get empty result when community stake not configured', async () => { - const qr = await query(GetCommunityStake, { + const qr = await query(GetCommunityStake(), { actor: context.actor, payload: { community_id: 'edgeware' }, }); diff --git a/libs/model/src/comment/CreateComment.command.ts b/libs/model/src/comment/CreateComment.command.ts index 82526202a29..3cfe9e49855 100644 --- a/libs/model/src/comment/CreateComment.command.ts +++ b/libs/model/src/comment/CreateComment.command.ts @@ -8,16 +8,18 @@ const schema = z.object({ content: z.string(), }); -export const CreateComment: CommandMetadata = - { - schema, - auth: [], - body: async ({ id, payload }) => { - const comment = await models.Comment.findOne({ where: { id } }); +export const CreateComment = (): CommandMetadata< + CommentAttributes, + typeof schema +> => ({ + schema, + auth: [], + body: async ({ id, payload }) => { + const comment = await models.Comment.findOne({ where: { id } }); - mustNotExist('Comment', comment); + mustNotExist('Comment', comment); - //await models.Comment.create(payload) - return payload as Partial; - }, - }; + //await models.Comment.create(payload) + return payload as Partial; + }, +}); diff --git a/libs/model/src/community/CreateCommunity.command.ts b/libs/model/src/community/CreateCommunity.command.ts index 1d507e05170..7f902a97667 100644 --- a/libs/model/src/community/CreateCommunity.command.ts +++ b/libs/model/src/community/CreateCommunity.command.ts @@ -56,10 +56,10 @@ const schema = z.object({ export type CreateCommunity = z.infer; -export const CreateCommunity: CommandMetadata< +export const CreateCommunity = (): CommandMetadata< CommunityAttributes, typeof schema -> = { +> => ({ schema, auth: [], body: async ({ id, payload }) => { @@ -70,4 +70,4 @@ export const CreateCommunity: CommandMetadata< //await models.Community.create(payload) return payload as Partial; }, -}; +}); diff --git a/libs/model/src/community/CreateGroup.command.ts b/libs/model/src/community/CreateGroup.command.ts index 265b4d10ac3..2ca0bc789b5 100644 --- a/libs/model/src/community/CreateGroup.command.ts +++ b/libs/model/src/community/CreateGroup.command.ts @@ -3,6 +3,7 @@ import { Op } from 'sequelize'; import { z } from 'zod'; import { models, sequelize } from '../database'; import { isCommunityAdminOrModerator } from '../middleware'; +import { mustNotExist } from '../middleware/guards'; import { CommunityAttributes, GroupAttributes } from '../models'; import { Requirement } from './Requirements.schema'; @@ -21,80 +22,81 @@ export const MAX_GROUPS_PER_COMMUNITY = 20; export const Errors = { MaxGroups: 'Exceeded max number of groups', InvalidTopics: 'Invalid topics', - GroupAlreadyExists: 'Group already exists', }; -export const CreateGroup: CommandMetadata = - { - schema, - auth: [isCommunityAdminOrModerator], - body: async ({ id, payload }) => { - const groups = await models.Group.findAll({ - where: { community_id: id }, - attributes: ['metadata'], - raw: true, - }); +export const CreateGroup = (): CommandMetadata< + CommunityAttributes, + typeof schema +> => ({ + schema, + auth: [isCommunityAdminOrModerator], + body: async ({ id, payload }) => { + const groups = await models.Group.findAll({ + where: { community_id: id }, + attributes: ['metadata'], + raw: true, + }); - if (groups.find((g) => g.metadata.name === payload.metadata.name)) - throw new InvalidState(Errors.GroupAlreadyExists); + mustNotExist( + 'Group', + groups.find((g) => g.metadata.name === payload.metadata.name), + ); - if (groups.length >= MAX_GROUPS_PER_COMMUNITY) - throw new InvalidState(Errors.MaxGroups); + if (groups.length >= MAX_GROUPS_PER_COMMUNITY) + throw new InvalidState(Errors.MaxGroups); - const topicsToAssociate = await models.Topic.findAll({ - where: { - id: { - [Op.in]: payload.topics || [], - }, - community_id: id, + const topicsToAssociate = await models.Topic.findAll({ + where: { + id: { + [Op.in]: payload.topics || [], }, - }); - if (payload.topics?.length !== topicsToAssociate.length) - throw new InvalidState(Errors.InvalidTopics); + community_id: id, + }, + }); + if (payload.topics?.length !== topicsToAssociate.length) + throw new InvalidState(Errors.InvalidTopics); - const newGroup = await models.sequelize.transaction( - async (transaction) => { - // create group - const group = await models.Group.create( - { - community_id: id!, - metadata: payload.metadata, - requirements: payload.requirements, - } as GroupAttributes, - { transaction }, - ); - if (topicsToAssociate.length > 0) { - // add group to all specified topics - await models.Topic.update( - { - group_ids: sequelize.fn( - 'array_append', - sequelize.col('group_ids'), - group.id, - ), - }, - { - where: { - id: { - [Op.in]: topicsToAssociate.map(({ id }) => id!), - }, - }, - transaction, - }, - ); - } - return group.toJSON(); - }, + const newGroup = await models.sequelize.transaction(async (transaction) => { + // create group + const group = await models.Group.create( + { + community_id: id!, + metadata: payload.metadata, + requirements: payload.requirements, + } as GroupAttributes, + { transaction }, ); + if (topicsToAssociate.length > 0) { + // add group to all specified topics + await models.Topic.update( + { + group_ids: sequelize.fn( + 'array_append', + sequelize.col('group_ids'), + group.id, + ), + }, + { + where: { + id: { + [Op.in]: topicsToAssociate.map(({ id }) => id!), + }, + }, + transaction, + }, + ); + } + return group.toJSON(); + }); - // TODO: create domain service to refresh community memberships - // TODO: create integration policy to connect creation events (like groups) to service above - // TODO: creation integration test that validates this refresh flow - //.refreshCommunityMemberships({ - // communityId: id, - // groupId: newGroup.id, - // }) + // TODO: create domain service to refresh community memberships + // TODO: create integration policy to connect creation events (like groups) to service above + // TODO: creation integration test that validates this refresh flow + //.refreshCommunityMemberships({ + // communityId: id, + // groupId: newGroup.id, + // }) - return { id, groups: [newGroup] }; - }, - }; + return { id, groups: [newGroup] }; + }, +}); diff --git a/libs/model/src/community/Demo.command.ts b/libs/model/src/community/Demo.command.ts deleted file mode 100644 index cd6c17c79c0..00000000000 --- a/libs/model/src/community/Demo.command.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CommandMetadata } from '@hicommonwealth/core'; -import { z } from 'zod'; - -const schema = z.object({ - numItems: z.coerce.number().int(), -}); - -export type Demo = z.infer; - -export const Demo: CommandMetadata<{ numItems: number }, typeof schema> = { - schema, - auth: [], - body: async ({ payload }) => { - return { - numItems: payload.numItems, - }; - }, -}; diff --git a/libs/model/src/community/GetCommunityStake.query.ts b/libs/model/src/community/GetCommunityStake.query.ts index 85dced05d66..4046a233b98 100644 --- a/libs/model/src/community/GetCommunityStake.query.ts +++ b/libs/model/src/community/GetCommunityStake.query.ts @@ -8,10 +8,10 @@ const schema = z.object({ stake_id: z.coerce.number().int().optional(), }); -export const GetCommunityStake: QueryMetadata< +export const GetCommunityStake = (): QueryMetadata< CommunityStakeAttributes, typeof schema -> = { +> => ({ schema, auth: [], body: async ({ payload }) => { @@ -28,4 +28,4 @@ export const GetCommunityStake: QueryMetadata< }) )?.get({ plain: true }); }, -}; +}); diff --git a/libs/model/src/community/SetCommunityStake.command.ts b/libs/model/src/community/SetCommunityStake.command.ts index 888ded4ce68..615cb58afc6 100644 --- a/libs/model/src/community/SetCommunityStake.command.ts +++ b/libs/model/src/community/SetCommunityStake.command.ts @@ -15,10 +15,10 @@ const schema = z.object({ export type SetCommunityStake = z.infer; -export const SetCommunityStake: CommandMetadata< +export const SetCommunityStake = (): CommandMetadata< CommunityAttributes, typeof schema -> = { +> => ({ schema, auth: [isCommunityAdmin], body: async ({ id, payload }) => { @@ -64,4 +64,4 @@ export const SetCommunityStake: CommandMetadata< CommunityStakes: [updated.get({ plain: true })], }; }, -}; +}); diff --git a/libs/model/src/community/UpdateCommunity.command.ts b/libs/model/src/community/UpdateCommunity.command.ts index bdd233351d4..34f4390690c 100644 --- a/libs/model/src/community/UpdateCommunity.command.ts +++ b/libs/model/src/community/UpdateCommunity.command.ts @@ -12,10 +12,10 @@ export const schema = z.object({ address: z.string(), }); -export const UpdateCommunity: CommandMetadata< +export const UpdateCommunity = (): CommandMetadata< CommunityAttributes, typeof schema -> = { +> => ({ schema, auth: [isCommunityAdmin], body: async ({ id, payload }) => { @@ -33,4 +33,4 @@ export const UpdateCommunity: CommandMetadata< community.namespace = payload.namespace; return (await community.save()).get({ plain: true }); }, -}; +}); diff --git a/libs/model/src/community/index.ts b/libs/model/src/community/index.ts index 7a0e47a16e4..41844cbc6f7 100644 --- a/libs/model/src/community/index.ts +++ b/libs/model/src/community/index.ts @@ -1,6 +1,5 @@ export * from './CreateCommunity.command'; export * from './CreateGroup.command'; -export * from './Demo.command'; export * from './GetCommunityStake.query'; export * from './SetCommunityStake.command'; export * from './UpdateCommunity.command'; diff --git a/libs/model/src/reaction/CreateReaction.command.ts b/libs/model/src/reaction/CreateReaction.command.ts index 9b6538854bb..159bf47e036 100644 --- a/libs/model/src/reaction/CreateReaction.command.ts +++ b/libs/model/src/reaction/CreateReaction.command.ts @@ -8,10 +8,10 @@ export const schema = z.object({ content: z.string(), }); -export const CreateReaction: CommandMetadata< +export const CreateReaction = (): CommandMetadata< ReactionAttributes, typeof schema -> = { +> => ({ schema, auth: [], body: async ({ id, payload }) => { @@ -22,4 +22,4 @@ export const CreateReaction: CommandMetadata< //await models.Reaction.create(payload) return payload as Partial; }, -}; +}); diff --git a/libs/model/src/thread/CreateThread.command.ts b/libs/model/src/thread/CreateThread.command.ts index d462272ba8f..705c1432395 100644 --- a/libs/model/src/thread/CreateThread.command.ts +++ b/libs/model/src/thread/CreateThread.command.ts @@ -8,7 +8,10 @@ export const schema = z.object({ content: z.string(), }); -export const CreateThread: CommandMetadata = { +export const CreateThread = (): CommandMetadata< + ThreadAttributes, + typeof schema +> => ({ schema, auth: [], body: async ({ id, payload }) => { @@ -19,4 +22,4 @@ export const CreateThread: CommandMetadata = { //await models.Thread.create(payload) return payload as Partial; }, -}; +}); diff --git a/libs/model/src/user/CreateUser.command.ts b/libs/model/src/user/CreateUser.command.ts index 7e9857f4771..7f510f26efd 100644 --- a/libs/model/src/user/CreateUser.command.ts +++ b/libs/model/src/user/CreateUser.command.ts @@ -8,7 +8,10 @@ export const schema = z.object({ content: z.string(), }); -export const CreateUser: CommandMetadata = { +export const CreateUser = (): CommandMetadata< + UserAttributes, + typeof schema +> => ({ schema, auth: [], body: async ({ id, payload }) => { @@ -19,4 +22,4 @@ export const CreateUser: CommandMetadata = { //await models.User.create(payload) return payload as Partial; }, -}; +}); diff --git a/packages/commonwealth/server/routes/communities/create_community_handler.ts b/packages/commonwealth/server/routes/communities/create_community_handler.ts index 12aed753887..86fee6d3416 100644 --- a/packages/commonwealth/server/routes/communities/create_community_handler.ts +++ b/packages/commonwealth/server/routes/communities/create_community_handler.ts @@ -24,7 +24,7 @@ export const createCommunityHandler = async ( } const validationResult = - await Community.CreateCommunity.schema.safeParseAsync(req.body); + await Community.CreateCommunity().schema.safeParseAsync(req.body); if (validationResult.success === false) { throw new AppError(formatErrorPretty(validationResult)); diff --git a/packages/commonwealth/server/routes/ddd/community.ts b/packages/commonwealth/server/routes/ddd/community.ts index a9fd1521d41..9dc715bb927 100644 --- a/packages/commonwealth/server/routes/ddd/community.ts +++ b/packages/commonwealth/server/routes/ddd/community.ts @@ -13,30 +13,20 @@ const router = Router(); router.get( '/:community_id/stake/:stake_id?', passport.authenticate('jwt', { session: false }), - expressQuery(Community.GetCommunityStake), + expressQuery(Community.GetCommunityStake()), ); router.put( '/:id/stake', passport.authenticate('jwt', { session: false }), - expressCommand(Community.SetCommunityStake), + expressCommand(Community.SetCommunityStake()), ); router.post( '/:id/group', passport.authenticate('jwt', { session: false }), analyticsMiddleware(MixpanelCommunityInteractionEvent.CREATE_GROUP), - expressCommand(Community.CreateGroup), -); - -router.post( - '/demo', - analyticsMiddleware('Demo Event', (results) => { - return { - x: results.numItems, - }; - }), - expressCommand(Community.Demo), + expressCommand(Community.CreateGroup()), ); export default router;