diff --git a/distributor-node/src/services/networking/query-node/schema.graphql b/distributor-node/src/services/networking/query-node/schema.graphql index dfa50ea0d9..dd02c8b1f4 100644 --- a/distributor-node/src/services/networking/query-node/schema.graphql +++ b/distributor-node/src/services/networking/query-node/schema.graphql @@ -2024,6 +2024,8 @@ type MemberMetadata implements BaseGraphQLObject { about: String member: Membership externalResources: [MembershipExternalResource!] + isVerifiedValidator: Boolean + validatorAccount: String membercreatedeventmetadata: [MemberCreatedEvent!] memberinvitedeventmetadata: [MemberInvitedEvent!] memberprofileupdatedeventnewMetadata: [MemberProfileUpdatedEvent!] @@ -17711,6 +17713,13 @@ input MemberMetadataWhereInput { about_startsWith: String about_endsWith: String about_in: [String!] + isVerifiedValidator_eq: Boolean + isVerifiedValidator_in: [Boolean!] + validatorAccount_eq: String + validatorAccount_contains: String + validatorAccount_startsWith: String + validatorAccount_endsWith: String + validatorAccount_in: [String!] member: MembershipWhereInput externalResources_none: MembershipExternalResourceWhereInput externalResources_some: MembershipExternalResourceWhereInput @@ -17743,12 +17752,16 @@ input MemberMetadataCreateInput { name: String avatar: JSONObject! about: String + isVerifiedValidator: Boolean + validatorAccount: String } input MemberMetadataUpdateInput { name: String avatar: JSONObject about: String + isVerifiedValidator: Boolean + validatorAccount: String } input MembershipGiftedEventWhereInput { @@ -30451,6 +30464,10 @@ enum MemberMetadataOrderByInput { name_DESC about_ASC about_DESC + isVerifiedValidator_ASC + isVerifiedValidator_DESC + validatorAccount_ASC + validatorAccount_DESC } enum MemberProfileUpdatedEventOrderByInput { diff --git a/metadata-protobuf/proto/Membership.proto b/metadata-protobuf/proto/Membership.proto index f12644d1bb..cda8600b36 100644 --- a/metadata-protobuf/proto/Membership.proto +++ b/metadata-protobuf/proto/Membership.proto @@ -25,5 +25,10 @@ message MembershipMetadata { optional ResourceType type = 1; optional string value = 2; } + repeated ExternalResource externalResources = 5; + + optional string validatorAccount = 6; } + + diff --git a/query-node/mappings/src/membership.ts b/query-node/mappings/src/membership.ts index 32e7c777a3..cd99d3b53c 100644 --- a/query-node/mappings/src/membership.ts +++ b/query-node/mappings/src/membership.ts @@ -158,6 +158,8 @@ async function saveMembershipMetadata( id: undefined, avatar, externalResources: undefined, + isVerifiedValidator: false, + validatorAccount: metadata?.validatorAccount || undefined, }) await store.save(metadataEntity) @@ -194,7 +196,9 @@ async function createNewMemberFromParams( entry: entryMethod, referredBy: entryMethod.isTypeOf === 'MembershipEntryPaid' && (params as BuyMembershipParameters).referrerId.isSome - ? new Membership({ id: (params as BuyMembershipParameters).referrerId.unwrap().toString() }) + ? new Membership({ + id: (params as BuyMembershipParameters).referrerId.unwrap().toString(), + }) : undefined, isVerified: isFoundingMember, inviteCount, @@ -204,7 +208,9 @@ async function createNewMemberFromParams( referredMembers: [], invitedBy: entryMethod.isTypeOf === 'MembershipEntryInvited' - ? new Membership({ id: (params as InviteMembershipParameters).invitingMemberId.toString() }) + ? new Membership({ + id: (params as InviteMembershipParameters).invitingMemberId.toString(), + }) : undefined, isFoundingMember, isCouncilMember: false, @@ -326,6 +332,14 @@ export async function members_MemberProfileUpdated({ store, event }: EventContex } } + if ( + typeof metadata?.validatorAccount === 'string' && + metadata.validatorAccount !== member.metadata.validatorAccount + ) { + member.metadata.validatorAccount = (metadata.validatorAccount || null) as string | undefined + member.metadata.isVerifiedValidator = false + } + if (newHandle.isSome) { member.handle = bytesToString(newHandle.unwrap()) } diff --git a/query-node/schemas/content.graphql b/query-node/schemas/content.graphql index 0628d31055..d4a2e5e319 100644 --- a/query-node/schemas/content.graphql +++ b/query-node/schemas/content.graphql @@ -80,7 +80,7 @@ type Channel @entity { "List of all claimed rewards" claimedRewards: [ChannelRewardClaimedEvent!] @derivedFrom(field: "channel") - + "Number of videos ever created in this channel" totalVideosCreated: Int! } diff --git a/query-node/schemas/membership.graphql b/query-node/schemas/membership.graphql index f77cd2e99d..c517130e4c 100644 --- a/query-node/schemas/membership.graphql +++ b/query-node/schemas/membership.graphql @@ -47,6 +47,9 @@ type MemberMetadata @entity { "Social media handles, email address..." externalResources: [MembershipExternalResource] @derivedFrom(field: "memberMetadata") + + isVerifiedValidator: Boolean + validatorAccount: String } type MembershipEntryPaid @variant { diff --git a/storage-node/src/services/queryNode/schema.graphql b/storage-node/src/services/queryNode/schema.graphql index dfa50ea0d9..dd02c8b1f4 100644 --- a/storage-node/src/services/queryNode/schema.graphql +++ b/storage-node/src/services/queryNode/schema.graphql @@ -2024,6 +2024,8 @@ type MemberMetadata implements BaseGraphQLObject { about: String member: Membership externalResources: [MembershipExternalResource!] + isVerifiedValidator: Boolean + validatorAccount: String membercreatedeventmetadata: [MemberCreatedEvent!] memberinvitedeventmetadata: [MemberInvitedEvent!] memberprofileupdatedeventnewMetadata: [MemberProfileUpdatedEvent!] @@ -17711,6 +17713,13 @@ input MemberMetadataWhereInput { about_startsWith: String about_endsWith: String about_in: [String!] + isVerifiedValidator_eq: Boolean + isVerifiedValidator_in: [Boolean!] + validatorAccount_eq: String + validatorAccount_contains: String + validatorAccount_startsWith: String + validatorAccount_endsWith: String + validatorAccount_in: [String!] member: MembershipWhereInput externalResources_none: MembershipExternalResourceWhereInput externalResources_some: MembershipExternalResourceWhereInput @@ -17743,12 +17752,16 @@ input MemberMetadataCreateInput { name: String avatar: JSONObject! about: String + isVerifiedValidator: Boolean + validatorAccount: String } input MemberMetadataUpdateInput { name: String avatar: JSONObject about: String + isVerifiedValidator: Boolean + validatorAccount: String } input MembershipGiftedEventWhereInput { @@ -30451,6 +30464,10 @@ enum MemberMetadataOrderByInput { name_DESC about_ASC about_DESC + isVerifiedValidator_ASC + isVerifiedValidator_DESC + validatorAccount_ASC + validatorAccount_DESC } enum MemberProfileUpdatedEventOrderByInput { diff --git a/tests/network-tests/src/fixtures/membership/GiftMembershipHappyCaseFixture.ts b/tests/network-tests/src/fixtures/membership/GiftMembershipHappyCaseFixture.ts index 989a098b6e..a9e0edd29a 100644 --- a/tests/network-tests/src/fixtures/membership/GiftMembershipHappyCaseFixture.ts +++ b/tests/network-tests/src/fixtures/membership/GiftMembershipHappyCaseFixture.ts @@ -58,7 +58,7 @@ export class GiftMembershipHappyCaseFixture extends StandardizedFixture { handle, rootAccount, controllerAccount, - metadata: { name, about, avatar, externalResources }, + metadata: { name, about, avatar, externalResources, validatorAccount }, isVerified, isFoundingMember, entry, @@ -81,6 +81,7 @@ export class GiftMembershipHappyCaseFixture extends StandardizedFixture { Utils.assert(entry.__typename === 'MembershipEntryGifted', 'Query node: Invalid membership entry method') Utils.assert(entry.membershipGiftedEvent) assert.equal(entry.membershipGiftedEvent.id, qEvent.id) + assert.equal(validatorAccount, metadata.validatorAccount) }) } diff --git a/tests/network-tests/src/fixtures/membership/InviteMembersHappyCaseFixture.ts b/tests/network-tests/src/fixtures/membership/InviteMembersHappyCaseFixture.ts index 0558f94721..07252bb128 100644 --- a/tests/network-tests/src/fixtures/membership/InviteMembersHappyCaseFixture.ts +++ b/tests/network-tests/src/fixtures/membership/InviteMembersHappyCaseFixture.ts @@ -61,7 +61,7 @@ export class InviteMembersHappyCaseFixture extends StandardizedFixture { handle, rootAccount, controllerAccount, - metadata: { name, about, avatar, externalResources }, + metadata: { name, about, avatar, externalResources, validatorAccount }, isVerified, entry, invitedBy, @@ -75,6 +75,7 @@ export class InviteMembersHappyCaseFixture extends StandardizedFixture { assert.equal(about, metadata.about) assert.equal(inviteCount, 0) assert.equal(avatar?.avatarUri, metadata.avatarUri || undefined) + assert.equal(validatorAccount, metadata.validatorAccount) assert.includeDeepMembers( externalResources ?? [], metadata.externalResources?.map(asMembershipExternalResource) ?? [] diff --git a/tests/network-tests/src/fixtures/membership/UpdateProfileHappyCaseFixture.ts b/tests/network-tests/src/fixtures/membership/UpdateProfileHappyCaseFixture.ts index e021cc1be6..859be58afd 100644 --- a/tests/network-tests/src/fixtures/membership/UpdateProfileHappyCaseFixture.ts +++ b/tests/network-tests/src/fixtures/membership/UpdateProfileHappyCaseFixture.ts @@ -16,6 +16,7 @@ export type MemberProfileData = { about?: string | null avatarUri?: string | null externalResources?: MembershipMetadata.IExternalResource[] | null + validatorAccount?: string | null } export class UpdateProfileHappyCaseFixture extends BaseQueryNodeFixture { @@ -55,6 +56,8 @@ export class UpdateProfileHappyCaseFixture extends BaseQueryNodeFixture { expected.externalResources?.map(asMembershipExternalResource) ?? [] ) assert.isFalse(Utils.hasDuplicates(metadata.externalResources?.map(({ type }) => type))) + assert.equal(metadata.isVerifiedValidator, false) + assert.equal(metadata.validatorAccount, expected.validatorAccount) } public getExpectedValues(): MemberProfileData { @@ -66,6 +69,9 @@ export class UpdateProfileHappyCaseFixture extends BaseQueryNodeFixture { externalResources: isSet(this.newValues.externalResources) ? this.newValues.externalResources || null : this.oldValues.externalResources, + validatorAccount: isSet(this.newValues.validatorAccount) + ? this.newValues.validatorAccount || null + : this.oldValues.validatorAccount, } } @@ -101,6 +107,7 @@ export class UpdateProfileHappyCaseFixture extends BaseQueryNodeFixture { about: this.newValues.about, avatarUri: this.newValues.avatarUri, externalResources: this.newValues.externalResources, + validatorAccount: this.newValues.validatorAccount, }) this.tx = this.api.tx.members.updateProfile( this.memberContext.memberId, diff --git a/tests/network-tests/src/fixtures/membership/utils.ts b/tests/network-tests/src/fixtures/membership/utils.ts index 79b786311f..e199e637eb 100644 --- a/tests/network-tests/src/fixtures/membership/utils.ts +++ b/tests/network-tests/src/fixtures/membership/utils.ts @@ -19,6 +19,7 @@ type MemberCreationParams = { externalResources?: MembershipMetadata.IExternalResource[] | null metadata: Bytes is_founding_member: boolean + validatorAccount?: string } // Common code for Membership fixtures @@ -28,13 +29,18 @@ export function generateParamsFromAccountId(accountId: string, isFoundingMember const about = `about${affix}` const avatarUri = `https://example.com/${affix}.jpg` const externalResources = [ - { type: MembershipMetadata.ExternalResource.ResourceType.HYPERLINK, value: `https://${affix}.com` }, + { + type: MembershipMetadata.ExternalResource.ResourceType.HYPERLINK, + value: `https://${affix}.com`, + }, ] + const validatorAccount = `validator address` const metadataBytes = Utils.metadataToBytes(MembershipMetadata, { name, about, avatarUri, externalResources, + validatorAccount, }) return { @@ -45,6 +51,7 @@ export function generateParamsFromAccountId(accountId: string, isFoundingMember about, avatarUri, externalResources, + validatorAccount, metadata: metadataBytes, is_founding_member: isFoundingMember, } diff --git a/tests/network-tests/src/flows/membership/updatingProfile.ts b/tests/network-tests/src/flows/membership/updatingProfile.ts index 7bc947edac..385a210740 100644 --- a/tests/network-tests/src/flows/membership/updatingProfile.ts +++ b/tests/network-tests/src/flows/membership/updatingProfile.ts @@ -18,15 +18,27 @@ export default async function updatingProfile({ api, query }: FlowProps): Promis const updates: MemberProfileData[] = [ // Partial updates // FIXME: Currently handle always need to be provided, see: https://github.com/Joystream/joystream/issues/2503 - { handle: 'New handle 1', name: 'New name' }, - { handle: 'New handle 2' }, + { + handle: 'New handle 1', + name: 'New name', + validatorAccount: 'validator address', + }, + { + handle: 'New handle 2', + }, // Setting metadata to null { handle: 'New handle 3', name: '', about: '', avatarUri: '', - externalResources: [{ type: MembershipMetadata.ExternalResource.ResourceType.EMAIL, value: 'A@example.com' }], + externalResources: [ + { + type: MembershipMetadata.ExternalResource.ResourceType.EMAIL, + value: 'A@example.com', + }, + ], + validatorAccount: '', }, // Full update { @@ -35,8 +47,14 @@ export default async function updatingProfile({ api, query }: FlowProps): Promis about: 'Updated about', avatarUri: 'https://example.com/updated-avatar.jpg', externalResources: [ - { type: MembershipMetadata.ExternalResource.ResourceType.EMAIL, value: 'B@example.com' }, - { type: MembershipMetadata.ExternalResource.ResourceType.HYPERLINK, value: 'example.com' }, + { + type: MembershipMetadata.ExternalResource.ResourceType.EMAIL, + value: 'B@example.com', + }, + { + type: MembershipMetadata.ExternalResource.ResourceType.HYPERLINK, + value: 'example.com', + }, ], }, ] @@ -50,6 +68,7 @@ export default async function updatingProfile({ api, query }: FlowProps): Promis for (const newValues of updates) { const context = { account, memberId } const updateProfileHappyCaseFixture = new UpdateProfileHappyCaseFixture(api, query, context, oldValues, newValues) + await new FixtureRunner(updateProfileHappyCaseFixture).runWithQueryNodeChecks() oldValues = updateProfileHappyCaseFixture.getExpectedValues() } diff --git a/tests/network-tests/src/graphql/queries/membership.graphql b/tests/network-tests/src/graphql/queries/membership.graphql index 2667a151ea..77f0a197f8 100644 --- a/tests/network-tests/src/graphql/queries/membership.graphql +++ b/tests/network-tests/src/graphql/queries/membership.graphql @@ -10,6 +10,8 @@ fragment MemberMetadataFields on MemberMetadata { type value } + isVerifiedValidator + validatorAccount } fragment MembershipFields on Membership {