From 21fc5b016d4798b4732221042566e80f79bb6957 Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Wed, 22 Nov 2023 09:21:16 +0100 Subject: [PATCH 1/8] fix: :bug: unitialized access video posted + misc --- src/mappings/content/utils.ts | 1 + src/mappings/content/video.ts | 1 + src/utils/notification/notificationsData.ts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mappings/content/utils.ts b/src/mappings/content/utils.ts index 2303f7e31..5e27782a0 100644 --- a/src/mappings/content/utils.ts +++ b/src/mappings/content/utils.ts @@ -75,6 +75,7 @@ import { createType } from '@joystream/types' import { EntityManager } from 'typeorm' import BN from 'bn.js' import { addNotification } from '../../utils/notification' +import fs from 'fs' // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AsDecoded = MetaClass extends { create: (props?: infer I) => any } diff --git a/src/mappings/content/video.ts b/src/mappings/content/video.ts index f8104dcb4..ab704811d 100644 --- a/src/mappings/content/video.ts +++ b/src/mappings/content/video.ts @@ -129,6 +129,7 @@ export async function processVideoCreatedEvent({ channelTitle: parseChannelTitle(channel), videoTitle: parseVideoTitle(video), videoId: video.id, + channelId: channel.id, }) await notifyChannelFollowers(overlay, channel.id, notificationData, eventEntity) diff --git a/src/utils/notification/notificationsData.ts b/src/utils/notification/notificationsData.ts index 6d9f005b7..6e746b0ce 100644 --- a/src/utils/notification/notificationsData.ts +++ b/src/utils/notification/notificationsData.ts @@ -90,7 +90,7 @@ export const getNotificationData = async ( icon: await getNotificationIcon(em, 'nft-alt'), link: await getNotificationLink(em, 'nft-page', [videoId]), avatar: await getNotificationAvatar(em, 'membershipId', newBidderId), - text: `${newBidderHandle} placed a higher bid in the auction for NFT: “${videoTitle}”`, + text: `${newBidderHandle} placed a higher bid in the timed auction for NFT: “${videoTitle}”`, } } case 'AuctionWon': { From 2a6df5d67032d0fa8651b3ab9ad80cf0cdb4a794 Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Fri, 24 Nov 2023 15:41:08 +0100 Subject: [PATCH 2/8] feat: :zap: batch channel verification --- .../resolvers/ChannelsResolver/index.ts | 94 ++++++++++--------- .../resolvers/ChannelsResolver/types.ts | 4 +- src/tests/integration/notifications.test.ts | 4 +- 3 files changed, 53 insertions(+), 49 deletions(-) diff --git a/src/server-extension/resolvers/ChannelsResolver/index.ts b/src/server-extension/resolvers/ChannelsResolver/index.ts index ca4adac52..f4fd77d2b 100644 --- a/src/server-extension/resolvers/ChannelsResolver/index.ts +++ b/src/server-extension/resolvers/ChannelsResolver/index.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' import { Args, Query, Mutation, Resolver, Info, Ctx, UseMiddleware } from 'type-graphql' -import { EntityManager, IsNull } from 'typeorm' +import { EntityManager, In, IsNull } from 'typeorm' import { ChannelNftCollector, ChannelNftCollectorsArgs, @@ -394,9 +394,9 @@ export class ChannelsResolver { @Mutation(() => VerifyChannelResult) @UseMiddleware(OperatorOnly()) - async verifyChannel(@Args() { channelId }: VerifyChannelArgs): Promise { + async verifyChannel(@Args() { channelIds }: VerifyChannelArgs): Promise { const em = await this.em() - return await verifyChannelService(em, channelId) + return await verifyChannelService(em, channelIds) } @Mutation(() => ExcludeChannelResult) @@ -468,51 +468,55 @@ export const excludeChannelService = async ( }) } -export const verifyChannelService = async (em: EntityManager, channelId: string) => { +export const verifyChannelService = async (em: EntityManager, channelIds: string[]) => { return withHiddenEntities(em, async () => { - const channel = await em.findOne(Channel, { - where: { id: channelId }, - }) - - if (!channel) { - throw new Error(`Channel by id ${channelId} not found!`) - } - // If channel already verified - return its data - if (channel.yppStatus.isTypeOf === 'YppVerified') { - const existingVerification = await em.getRepository(ChannelVerification).findOneOrFail({ - where: { channelId: channel.id }, - }) - return { - id: existingVerification.id, - channelId: channel.id, - createdAt: existingVerification.timestamp, - } - } - // othewise create new verification - const newVerification = new ChannelVerification({ - id: uniqueId(), - channelId: channel.id, - timestamp: new Date(), + const channels = await em.getRepository(Channel).find({ + where: { id: In(channelIds) }, }) - channel.yppStatus = new YppVerified({ verification: newVerification.id }) - await em.save([newVerification, channel]) - // in case account exist deposit notification - const channelOwnerMemberId = channel.ownerMemberId - if (channelOwnerMemberId) { - const account = await em.findOne(Account, { where: { membershipId: channelOwnerMemberId } }) - await addNotification( - em, - account, - new ChannelRecipient({ channel: channel.id }), - new ChannelVerified({}) - ) - } + const results = channels + .filter((channel) => channel) + .map(async (channel) => { + // If channel already verified - return its data + if (channel.yppStatus.isTypeOf === 'YppVerified') { + const existingVerification = await em.getRepository(ChannelVerification).findOneOrFail({ + where: { channelId: channel.id }, + }) + return { + id: existingVerification.id, + channelId: channel.id, + createdAt: existingVerification.timestamp, + } + } + // othewise create new verification regardless whether the channel was previously verified + const newVerification = new ChannelVerification({ + id: uniqueId(), + channelId: channel.id, + timestamp: new Date(), + }) + channel.yppStatus = new YppVerified({ verification: newVerification.id }) + await em.save([newVerification, channel]) + + // in case account exist deposit notification + const channelOwnerMemberId = channel.ownerMemberId + if (channelOwnerMemberId) { + const account = await em.findOne(Account, { + where: { membershipId: channelOwnerMemberId }, + }) + await addNotification( + em, + account, + new ChannelRecipient({ channel: channel.id }), + new ChannelVerified({}) + ) + } - return { - id: newVerification.id, - channelId: channel.id, - createdAt: newVerification.timestamp, - } + return { + id: newVerification.id, + channelId: channel.id, + createdAt: newVerification.timestamp, + } + }) + return await Promise.all(results) }) } diff --git a/src/server-extension/resolvers/ChannelsResolver/types.ts b/src/server-extension/resolvers/ChannelsResolver/types.ts index 35964bef8..82594cad2 100644 --- a/src/server-extension/resolvers/ChannelsResolver/types.ts +++ b/src/server-extension/resolvers/ChannelsResolver/types.ts @@ -186,8 +186,8 @@ export class SuspendChannelArgs { @ArgsType() export class VerifyChannelArgs { - @Field(() => String, { nullable: false }) - channelId!: string + @Field(() => [String], { nullable: false }) + channelIds!: string[] } @ObjectType() diff --git a/src/tests/integration/notifications.test.ts b/src/tests/integration/notifications.test.ts index 83e8f1cac..62d26861a 100644 --- a/src/tests/integration/notifications.test.ts +++ b/src/tests/integration/notifications.test.ts @@ -77,7 +77,7 @@ describe('notifications tests', () => { const channelId = '1' const nextNotificationIdPre = await getNextNotificationId(em, false) notificationId = OFFCHAIN_NOTIFICATION_ID_TAG + '-' + nextNotificationIdPre - await verifyChannelService(em, channelId) + await verifyChannelService(em, [channelId]) notification = await em.getRepository(Notification).findOneBy({ id: notificationId, @@ -108,7 +108,7 @@ describe('notifications tests', () => { it('verify channel should mark channel as excluded with entity inserted', async () => { const channelId = '2' - await verifyChannelService(em, channelId) + await verifyChannelService(em, [channelId]) const channel = await em.getRepository(Channel).findOneByOrFail({ id: channelId }) const channelVerification = await em From 44a38a113a472f60cb03a57862529728b78597f4 Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Mon, 27 Nov 2023 15:52:35 +0100 Subject: [PATCH 3/8] fix: :zap: remove non necessary import --- src/mappings/content/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mappings/content/utils.ts b/src/mappings/content/utils.ts index 5e27782a0..2303f7e31 100644 --- a/src/mappings/content/utils.ts +++ b/src/mappings/content/utils.ts @@ -75,7 +75,6 @@ import { createType } from '@joystream/types' import { EntityManager } from 'typeorm' import BN from 'bn.js' import { addNotification } from '../../utils/notification' -import fs from 'fs' // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AsDecoded = MetaClass extends { create: (props?: infer I) => any } From 31b637ac35ebba1973a1454c8a83abac85e221a4 Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Mon, 27 Nov 2023 17:08:05 +0100 Subject: [PATCH 4/8] feat: :sparkles: batch suspend channel and pLimits promises --- .../resolvers/ChannelsResolver/index.ts | 167 ++++++++++-------- .../resolvers/ChannelsResolver/types.ts | 4 +- 2 files changed, 93 insertions(+), 78 deletions(-) diff --git a/src/server-extension/resolvers/ChannelsResolver/index.ts b/src/server-extension/resolvers/ChannelsResolver/index.ts index f4fd77d2b..efb2966eb 100644 --- a/src/server-extension/resolvers/ChannelsResolver/index.ts +++ b/src/server-extension/resolvers/ChannelsResolver/index.ts @@ -54,6 +54,7 @@ import { AccountOnly, OperatorOnly, UserOnly } from '../middleware' import { addNotification } from '../../../utils/notification' import { assertNotNull } from '@subsquid/substrate-processor' import { FALLBACK_CHANNEL_TITLE } from '../../../mappings/content/utils' +import pLimit from 'p-limit' @Resolver() export class ChannelsResolver { @@ -341,54 +342,64 @@ export class ChannelsResolver { @Mutation(() => SuspendChannelResult) @UseMiddleware(OperatorOnly()) - async suspendChannel(@Args() { channelId }: SuspendChannelArgs): Promise { + async suspendChannels( + @Args() { channelIds }: SuspendChannelArgs + ): Promise { const em = await this.em() return withHiddenEntities(em, async () => { - const channel = await em.findOne(Channel, { - where: { id: channelId }, + const channels = await em.find(Channel, { + where: { id: In(channelIds) }, }) - if (!channel) { - throw new Error(`Channel by id ${channelId} not found!`) - } - // If channel already suspended - return its data - if (channel.yppStatus.isTypeOf === 'YppSuspended') { - const existingSuspension = await em.getRepository(ChannelSuspension).findOneOrFail({ - where: { id: channel.yppStatus.suspension }, + const suspendChannel = async (channel: Channel) => { + // If channel already suspended - return its data + if (channel.yppStatus.isTypeOf === 'YppSuspended') { + const existingSuspension = await em.getRepository(ChannelSuspension).findOneOrFail({ + where: { id: channel.yppStatus.suspension }, + }) + return { + id: existingSuspension.id, + channelId: channel.id, + createdAt: existingSuspension.timestamp, + } + } + // otherwise create a new suspension + const newSuspension = new ChannelSuspension({ + id: uniqueId(), + channelId: channel.id, + timestamp: new Date(), }) + channel.yppStatus = new YppSuspended({ suspension: newSuspension.id }) + await em.save([newSuspension, channel]) + + // in case account exist deposit notification + const channelOwnerMemberId = channel.ownerMemberId + if (channelOwnerMemberId) { + const account = await em.findOne(Account, { + where: { membershipId: channelOwnerMemberId }, + }) + await addNotification( + em, + account, + new ChannelRecipient({ channel: channel.id }), + new ChannelSuspended({}) + ) + } + return { - id: existingSuspension.id, + id: newSuspension.id, channelId: channel.id, - createdAt: existingSuspension.timestamp, + createdAt: newSuspension.timestamp, } } - // otherwise create a new suspension - const newSuspension = new ChannelSuspension({ - id: uniqueId(), - channelId: channel.id, - timestamp: new Date(), - }) - channel.yppStatus = new YppSuspended({ suspension: newSuspension.id }) - await em.save([newSuspension, channel]) - - // in case account exist deposit notification - const channelOwnerMemberId = channel.ownerMemberId - if (channelOwnerMemberId) { - const account = await em.findOne(Account, { where: { membershipId: channelOwnerMemberId } }) - await addNotification( - em, - account, - new ChannelRecipient({ channel: channel.id }), - new ChannelSuspended({}) - ) - } - return { - id: newSuspension.id, - channelId: channel.id, - createdAt: newSuspension.timestamp, - } + const limit = pLimit(5) // Limit to 5 concurrent promises + return await Promise.all( + channels + .filter((channel) => channel) + .map((channel) => limit(async () => await suspendChannel(channel))) + ) }) } @@ -474,49 +485,53 @@ export const verifyChannelService = async (em: EntityManager, channelIds: string where: { id: In(channelIds) }, }) - const results = channels - .filter((channel) => channel) - .map(async (channel) => { - // If channel already verified - return its data - if (channel.yppStatus.isTypeOf === 'YppVerified') { - const existingVerification = await em.getRepository(ChannelVerification).findOneOrFail({ - where: { channelId: channel.id }, - }) - return { - id: existingVerification.id, - channelId: channel.id, - createdAt: existingVerification.timestamp, - } - } - // othewise create new verification regardless whether the channel was previously verified - const newVerification = new ChannelVerification({ - id: uniqueId(), - channelId: channel.id, - timestamp: new Date(), + const verifyChannel = async (channel: Channel) => { + // If channel already verified - return its data + if (channel.yppStatus.isTypeOf === 'YppVerified') { + const existingVerification = await em.getRepository(ChannelVerification).findOneOrFail({ + where: { channelId: channel.id }, }) - channel.yppStatus = new YppVerified({ verification: newVerification.id }) - await em.save([newVerification, channel]) - - // in case account exist deposit notification - const channelOwnerMemberId = channel.ownerMemberId - if (channelOwnerMemberId) { - const account = await em.findOne(Account, { - where: { membershipId: channelOwnerMemberId }, - }) - await addNotification( - em, - account, - new ChannelRecipient({ channel: channel.id }), - new ChannelVerified({}) - ) - } - return { - id: newVerification.id, + id: existingVerification.id, channelId: channel.id, - createdAt: newVerification.timestamp, + createdAt: existingVerification.timestamp, } + } + // othewise create new verification regardless whether the channel was previously verified + const newVerification = new ChannelVerification({ + id: uniqueId(), + channelId: channel.id, + timestamp: new Date(), }) - return await Promise.all(results) + channel.yppStatus = new YppVerified({ verification: newVerification.id }) + await em.save([newVerification, channel]) + + // in case account exist deposit notification + const channelOwnerMemberId = channel.ownerMemberId + if (channelOwnerMemberId) { + const account = await em.findOne(Account, { + where: { membershipId: channelOwnerMemberId }, + }) + await addNotification( + em, + account, + new ChannelRecipient({ channel: channel.id }), + new ChannelVerified({}) + ) + } + + return { + id: newVerification.id, + channelId: channel.id, + createdAt: newVerification.timestamp, + } + } + + const limit = pLimit(5) // Limit to 5 concurrent promises + return await Promise.all( + channels + .filter((channel) => channel) + .map((channel) => limit(async () => await verifyChannel(channel))) + ) }) } diff --git a/src/server-extension/resolvers/ChannelsResolver/types.ts b/src/server-extension/resolvers/ChannelsResolver/types.ts index 82594cad2..d7ddc3283 100644 --- a/src/server-extension/resolvers/ChannelsResolver/types.ts +++ b/src/server-extension/resolvers/ChannelsResolver/types.ts @@ -180,8 +180,8 @@ export class ChannelsSearchArgs { @ArgsType() export class SuspendChannelArgs { - @Field(() => String, { nullable: false }) - channelId!: string + @Field(() => [String], { nullable: false }) + channelIds!: string[] } @ArgsType() From fa2073fbaabfea23b10461dcdd48529499514b03 Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Tue, 28 Nov 2023 09:02:22 +0100 Subject: [PATCH 5/8] fix: :bug: typegraphql return type --- .../resolvers/ChannelsResolver/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/server-extension/resolvers/ChannelsResolver/index.ts b/src/server-extension/resolvers/ChannelsResolver/index.ts index efb2966eb..06ab2a602 100644 --- a/src/server-extension/resolvers/ChannelsResolver/index.ts +++ b/src/server-extension/resolvers/ChannelsResolver/index.ts @@ -340,7 +340,7 @@ export class ChannelsResolver { }) } - @Mutation(() => SuspendChannelResult) + @Mutation(() => [SuspendChannelResult]) @UseMiddleware(OperatorOnly()) async suspendChannels( @Args() { channelIds }: SuspendChannelArgs @@ -395,10 +395,9 @@ export class ChannelsResolver { } const limit = pLimit(5) // Limit to 5 concurrent promises + const existingChannels = channels.filter((channel) => channel) return await Promise.all( - channels - .filter((channel) => channel) - .map((channel) => limit(async () => await suspendChannel(channel))) + existingChannels.map((channel) => limit(async () => await suspendChannel(channel))) ) }) } @@ -528,10 +527,9 @@ export const verifyChannelService = async (em: EntityManager, channelIds: string } const limit = pLimit(5) // Limit to 5 concurrent promises + const existingChannels = channels.filter((channel) => channel) return await Promise.all( - channels - .filter((channel) => channel) - .map((channel) => limit(async () => await verifyChannel(channel))) + existingChannels.map((channel) => limit(async () => await verifyChannel(channel))) ) }) } From cb6b0d2f8925f76b97b37446116db9ab6bdda07e Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Mon, 27 Nov 2023 18:09:10 +0100 Subject: [PATCH 6/8] Notifications/qa fixes (#255) * fix: :bug: unitialized access video posted + misc * feat: :zap: batch channel verification * fix: :zap: remove non necessary import * feat: :sparkles: batch suspend channel and pLimits promises --- src/server-extension/resolvers/ChannelsResolver/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server-extension/resolvers/ChannelsResolver/index.ts b/src/server-extension/resolvers/ChannelsResolver/index.ts index 06ab2a602..b503a8246 100644 --- a/src/server-extension/resolvers/ChannelsResolver/index.ts +++ b/src/server-extension/resolvers/ChannelsResolver/index.ts @@ -55,6 +55,7 @@ import { addNotification } from '../../../utils/notification' import { assertNotNull } from '@subsquid/substrate-processor' import { FALLBACK_CHANNEL_TITLE } from '../../../mappings/content/utils' import pLimit from 'p-limit' +import pLimit from 'p-limit' @Resolver() export class ChannelsResolver { From 447a268d490e7a7dee53db757ed16c479ae59002 Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Tue, 28 Nov 2023 09:06:36 +0100 Subject: [PATCH 7/8] fix: :art: import --- src/server-extension/resolvers/ChannelsResolver/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server-extension/resolvers/ChannelsResolver/index.ts b/src/server-extension/resolvers/ChannelsResolver/index.ts index b503a8246..06ab2a602 100644 --- a/src/server-extension/resolvers/ChannelsResolver/index.ts +++ b/src/server-extension/resolvers/ChannelsResolver/index.ts @@ -55,7 +55,6 @@ import { addNotification } from '../../../utils/notification' import { assertNotNull } from '@subsquid/substrate-processor' import { FALLBACK_CHANNEL_TITLE } from '../../../mappings/content/utils' import pLimit from 'p-limit' -import pLimit from 'p-limit' @Resolver() export class ChannelsResolver { From 45d999e9e66bc56f77e08aacefe0d28773f8564a Mon Sep 17 00:00:00 2001 From: Ignazio Bovo Date: Tue, 28 Nov 2023 09:10:15 +0100 Subject: [PATCH 8/8] style: :lipstick: format --- src/server-extension/resolvers/ChannelsResolver/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server-extension/resolvers/ChannelsResolver/index.ts b/src/server-extension/resolvers/ChannelsResolver/index.ts index b0e00251f..06ab2a602 100644 --- a/src/server-extension/resolvers/ChannelsResolver/index.ts +++ b/src/server-extension/resolvers/ChannelsResolver/index.ts @@ -530,7 +530,6 @@ export const verifyChannelService = async (em: EntityManager, channelIds: string const existingChannels = channels.filter((channel) => channel) return await Promise.all( existingChannels.map((channel) => limit(async () => await verifyChannel(channel))) - ) }) }