From e300f447011497d1056620d39a97e379959a0259 Mon Sep 17 00:00:00 2001 From: dayuy <973860441@qq.com> Date: Mon, 27 Feb 2023 15:58:17 +0800 Subject: [PATCH] fix: add fields createdByMe and iamInvolved of Network API(#22) --- src/channel/channel.resolver.ts | 70 ++++++++++++++++++- src/channel/channel.service.ts | 2 +- src/channel/models/channel.model.ts | 6 ++ src/ibppeer/ibppeer.resolver.ts | 12 ++++ src/ibppeer/ibppeer.service.ts | 7 ++ src/ibppeer/models/ibppeer.model.ts | 6 ++ src/network/models/network.model.ts | 9 +++ src/network/network.gql | 20 ++++++ src/network/network.module.ts | 3 +- src/network/network.resolver.ts | 31 ++++++++ src/organization/models/organization.model.ts | 2 +- src/schema.gql | 16 ++++- 12 files changed, 177 insertions(+), 7 deletions(-) diff --git a/src/channel/channel.resolver.ts b/src/channel/channel.resolver.ts index dd74e9d..f032d94 100644 --- a/src/channel/channel.resolver.ts +++ b/src/channel/channel.resolver.ts @@ -1,12 +1,22 @@ -import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { + Args, + Mutation, + Parent, + ResolveField, + Resolver, +} from '@nestjs/graphql'; +import DataLoader from 'dataloader'; +import { Loader } from 'src/common/dataloader'; import { Auth } from 'src/common/decorators/auth.decorator'; +import { Organization } from 'src/organization/models/organization.model'; +import { OrganizationLoader } from 'src/organization/organization.loader'; import { JwtAuth } from 'src/types'; import { ChannelService } from './channel.service'; import { NewChannel } from './dto/new-channel.input'; import { UpdateChannel } from './dto/update-channel.input'; import { Channel } from './models/channel.model'; -@Resolver() +@Resolver(() => Channel) export class ChannelResolver { constructor(private readonly channelService: ChannelService) {} @@ -27,4 +37,60 @@ export class ChannelResolver { ): Promise { return this.channelService.updateChannel(auth, name, channel); } + + @ResolveField(() => Boolean, { + description: '是否为我创建的', + }) + async createdByMe( + @Auth() auth: JwtAuth, + @Parent() channel: Channel, + @Loader(OrganizationLoader) + orgLoader: DataLoader, + ): Promise { + const { preferred_username } = auth; + const { members } = channel; + if (!members) return; + const orgs = await orgLoader.loadMany(members.map((m) => m.name)); + const orgMap = new Map( + (orgs as Organization[]).map((org) => [org.name, org]), + ); + let iCreated = false; + members.forEach((m) => { + if (orgMap.has(m.name)) { + const v = orgMap.get(m.name); + if (m.initiator && v.admin === preferred_username) { + iCreated = true; + } + } + }); + return iCreated; + } + + @ResolveField(() => Boolean, { + description: '是否为我参与的', + }) + async iamInvolved( + @Auth() auth: JwtAuth, + @Parent() channel: Channel, + @Loader(OrganizationLoader) + orgLoader: DataLoader, + ): Promise { + const { preferred_username } = auth; + const { members } = channel; + if (!members) return; + const orgs = await orgLoader.loadMany(members.map((m) => m.name)); + const orgMap = new Map( + (orgs as Organization[]).map((org) => [org.name, org]), + ); + let involved = false; + members.forEach((m) => { + if (orgMap.has(m.name)) { + const v = orgMap.get(m.name); + if (v.clients?.includes(preferred_username)) { + involved = true; + } + } + }); + return involved; + } } diff --git a/src/channel/channel.service.ts b/src/channel/channel.service.ts index 544df32..6e79b8e 100644 --- a/src/channel/channel.service.ts +++ b/src/channel/channel.service.ts @@ -63,7 +63,7 @@ export class ChannelService { channel; const members = (organizations || []) .concat(initiator) - .map((d) => ({ name: d })); + .map((d) => ({ name: d, initiator: d === initiator })); const k8s = await this.k8sService.getClient(auth); const { body } = await k8s.channel.create({ metadata: { diff --git a/src/channel/models/channel.model.ts b/src/channel/models/channel.model.ts index e60de7e..b7b7af9 100644 --- a/src/channel/models/channel.model.ts +++ b/src/channel/models/channel.model.ts @@ -23,4 +23,10 @@ export class Channel { /** 状态 */ @Field(() => ChannelStatus, { description: '状态' }) status?: string; + + /** 我创建的 */ + createdByMe?: boolean; + + /** 我参与的 */ + iamInvolved?: boolean; } diff --git a/src/ibppeer/ibppeer.resolver.ts b/src/ibppeer/ibppeer.resolver.ts index ee17850..a7cb1d5 100644 --- a/src/ibppeer/ibppeer.resolver.ts +++ b/src/ibppeer/ibppeer.resolver.ts @@ -118,4 +118,16 @@ export class IbppeerResolver { ?.filter((net) => find(net.channelNames, (o) => joinedChans.includes(o))) ?.map((net) => net.name); } + + @ResolveField(() => Boolean, { + description: '是否为我创建的', + }) + async createdByMe( + @Auth() auth: JwtAuth, + @Parent() ibppeer: Ibppeer, + ): Promise { + const { preferred_username } = auth; + const { enrolluser } = ibppeer; + return enrolluser === preferred_username; + } } diff --git a/src/ibppeer/ibppeer.service.ts b/src/ibppeer/ibppeer.service.ts index 95a56c3..81d8b17 100644 --- a/src/ibppeer/ibppeer.service.ts +++ b/src/ibppeer/ibppeer.service.ts @@ -36,6 +36,7 @@ export class IbppeerService { limits: ibppeer.spec?.resources?.peer?.limits, status: ibppeer.status?.type, namespace: ibppeer.metadata?.namespace, + enrolluser: ibppeer.spec?.secret?.enrollment?.component?.enrolluser, }; } @@ -45,6 +46,12 @@ export class IbppeerService { return body.items.map((item) => this.format(item)); } + async getIbppeer(auth: JwtAuth, org: string, name: string): Promise { + const k8s = await this.k8sService.getClient(auth); + const { body } = await k8s.ibppeer.read(name, org); + return this.format(body); + } + async createIbppeer(auth: JwtAuth, org: string): Promise { const { token, preferred_username } = auth; const { binaryData } = await this.cmService.getConfigmap( diff --git a/src/ibppeer/models/ibppeer.model.ts b/src/ibppeer/models/ibppeer.model.ts index dce56f8..c432f68 100644 --- a/src/ibppeer/models/ibppeer.model.ts +++ b/src/ibppeer/models/ibppeer.model.ts @@ -27,4 +27,10 @@ export class Ibppeer { /** 运行状态 */ @Field(() => IbppeerStatus, { description: '运行状态' }) status?: string; + + @HideField() + enrolluser?: string; + + /** 我创建的 */ + createdByMe?: boolean; } diff --git a/src/network/models/network.model.ts b/src/network/models/network.model.ts index 0f5ce77..cbbcdcc 100644 --- a/src/network/models/network.model.ts +++ b/src/network/models/network.model.ts @@ -1,9 +1,11 @@ import { Field, HideField, ID, ObjectType } from '@nestjs/graphql'; import { Channel } from 'src/channel/models/channel.model'; import { SpecResource } from 'src/common/models/spec-resource.model'; +import { Ibppeer } from 'src/ibppeer/models/ibppeer.model'; import { Organization } from 'src/organization/models/organization.model'; import { StatusType } from 'src/organization/models/status-type.enum'; import { AnyObj } from 'src/types'; +import { OrderVersion } from '../dto/order-version.enum'; @ObjectType({ description: '网络' }) export class Network { @@ -61,6 +63,13 @@ export class Network { /** 节点存储 */ storage?: string; + + /** 节点版本 */ + @Field(() => OrderVersion, { description: '节点版本' }) + version?: string; + + /** 网络中的所有节点 */ + peers?: Ibppeer[]; } @ObjectType({ description: '成员' }) diff --git a/src/network/network.gql b/src/network/network.gql index b86e7ca..0d7f236 100644 --- a/src/network/network.gql +++ b/src/network/network.gql @@ -19,6 +19,12 @@ query getNetworks{ status channels { name + createdByMe + iamInvolved + } + peers { + name + createdByMe } } } @@ -42,13 +48,25 @@ query getNetwork($name: String!) { } organizations { name + displayName admin + creationTimestamp + lastHeartbeatTime + status + reason + ibppeers { + name + } } initiator { name admin } status + peers { + name + createdByMe + } channels { name members{ @@ -60,6 +78,8 @@ query getNetwork($name: String!) { } creationTimestamp status + createdByMe + iamInvolved } } } diff --git a/src/network/network.module.ts b/src/network/network.module.ts index 305aa0d..dca4957 100644 --- a/src/network/network.module.ts +++ b/src/network/network.module.ts @@ -5,10 +5,11 @@ import { ProposalModule } from 'src/proposal/proposal.module'; import { NetworkLoader } from './network.loader'; import { FederationModule } from 'src/federation/federation.module'; import { ChannelModule } from 'src/channel/channel.module'; +import { IbppeerModule } from 'src/ibppeer/ibppeer.module'; @Module({ providers: [NetworkService, NetworkResolver, NetworkLoader], exports: [NetworkLoader], - imports: [ProposalModule, FederationModule, ChannelModule], // TODO: 使用ChannelLoader后,去掉ChannelModule + imports: [ProposalModule, FederationModule, ChannelModule, IbppeerModule], // TODO: 使用ChannelLoader后,去掉ChannelModule, IbppeerModule }) export class NetworkModule {} diff --git a/src/network/network.resolver.ts b/src/network/network.resolver.ts index f3d6ac0..3d9faa7 100644 --- a/src/network/network.resolver.ts +++ b/src/network/network.resolver.ts @@ -7,12 +7,15 @@ import { Resolver, } from '@nestjs/graphql'; import DataLoader from 'dataloader'; +import { isEqual, uniqWith } from 'lodash'; // import { ChannelLoader } from 'src/channel/channel.loader'; import { ChannelService } from 'src/channel/channel.service'; import { Channel } from 'src/channel/models/channel.model'; import { Loader } from 'src/common/dataloader'; import { Auth } from 'src/common/decorators/auth.decorator'; import { NETWORK_VERSION_RESOURCES } from 'src/common/utils'; +import { IbppeerService } from 'src/ibppeer/ibppeer.service'; +import { Ibppeer } from 'src/ibppeer/models/ibppeer.model'; import { Organization } from 'src/organization/models/organization.model'; import { OrganizationLoader } from 'src/organization/organization.loader'; import { JwtAuth } from 'src/types'; @@ -26,6 +29,7 @@ export class NetworkResolver { constructor( private readonly networkService: NetworkService, private readonly channelService: ChannelService, + private readonly peerService: IbppeerService, ) {} @Query(() => [Network], { description: '网络列表' }) @@ -124,4 +128,31 @@ export class NetworkResolver { // const cs = await channelLoader.loadMany(channelNames); // return cs; } + + @ResolveField(() => [Ibppeer], { + nullable: true, + description: '网络中的所有节点', + }) + async peers( + @Auth() auth: JwtAuth, + @Parent() network: Network, + ): Promise { + const { channelNames } = network; + if (!channelNames || channelNames.length === 0) return; + // TODO: channelLoader + const channels = await this.channelService.getChannelsByNames( + auth, + channelNames, + ); + const peerses = channels?.map((channel) => channel.peers); + const peers = uniqWith(peerses.flat(), isEqual); + // TODO: IbppeerLoader, 以namespace/name为key + return Promise.all( + peers?.map((peer) => + this.peerService + .getIbppeer(auth, peer.namespace, peer.name) + .catch(() => peer), + ), + ); + } } diff --git a/src/organization/models/organization.model.ts b/src/organization/models/organization.model.ts index c1b45dc..910dc82 100644 --- a/src/organization/models/organization.model.ts +++ b/src/organization/models/organization.model.ts @@ -35,7 +35,7 @@ export class Organization { @Field(() => StatusType) status?: string; - /** 原因(状态为非Deplyed时) */ + /** 原因 */ reason?: string; /** 加入时间(只在联盟中使用) */ diff --git a/src/schema.gql b/src/schema.gql index ae6c702..d85e6e4 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -3,9 +3,15 @@ # ------------------------------------------------------ type Channel { + """我创建的""" + createdByMe: Boolean + """合约数量""" creationTimestamp: String + """我参与的""" + iamInvolved: Boolean + """组织数量""" members: [SpecMember!] @@ -91,6 +97,9 @@ type Ibppeer { """加入的通道""" channels: [String!] + """我创建的""" + createdByMe: Boolean + """创建时间""" creationTimestamp: String! @@ -250,13 +259,16 @@ type Network { """网络中组织""" organizations: [Organization!] + """网络中的所有节点""" + peers: [Ibppeer!] + """状态""" status: StatusType """节点存储""" storage: String - """配置版本""" + """节点版本""" version: OrderVersion } @@ -393,7 +405,7 @@ type Organization { """所在网络""" networks: [Network!] - """原因(状态为非Deplyed时)""" + """原因""" reason: String """状态"""