From 7a384f98a79d43a8b1e6eca60302faabb7387ce4 Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Sat, 10 Feb 2024 01:27:45 +0000 Subject: [PATCH] feat: set up syncing of bot members --- .../src/lib/api-bot-manager.service.ts | 59 +++++++++++++++---- .../src/lib/api-bot-member.service.ts | 50 +++++++++++++--- .../src/lib/api-network-asset-sync.service.ts | 2 +- .../src/lib/user-user-detail-feature.tsx | 21 +++++-- 4 files changed, 109 insertions(+), 23 deletions(-) diff --git a/libs/api/bot/data-access/src/lib/api-bot-manager.service.ts b/libs/api/bot/data-access/src/lib/api-bot-manager.service.ts index 110c5784..e3b4d728 100644 --- a/libs/api/bot/data-access/src/lib/api-bot-manager.service.ts +++ b/libs/api/bot/data-access/src/lib/api-bot-manager.service.ts @@ -1,4 +1,5 @@ import { Injectable, Logger, OnModuleInit } from '@nestjs/common' +import { Cron, CronExpression } from '@nestjs/schedule' import { Bot } from '@prisma/client' import { createDiscordRestClient, DiscordBot } from '@pubkey-link/api-bot-util' @@ -170,11 +171,6 @@ export class ApiBotManagerService implements OnModuleInit { } async syncBotServer(userId: string, botId: string, serverId: string) { - const bot = this.ensureBotInstance(botId) - if (!bot) { - console.log(`Can't find bot.`, botId, serverId) - return false - } const community = await this.core.data.community.findFirst({ where: { bot: { id: botId } }, include: { bot: true }, @@ -183,13 +179,48 @@ export class ApiBotManagerService implements OnModuleInit { console.log(`Can't find community.`, botId, serverId) return false } + await this.core.ensureCommunityAdmin({ userId, communityId: community.id }) + await this.syncBotServerMembers({ + communityId: community.id, + botId, + serverId, + }) + return true + } + + @Cron(CronExpression.EVERY_5_MINUTES) + private async syncBotServers() { + const bots = await this.core.data.bot.findMany({ where: { status: BotStatus.Active } }) + + for (const bot of bots) { + const servers = await this.getBotServers('-no-user-id-', bot.id) + for (const server of servers) { + await this.syncBotServerMembers({ botId: bot.id, communityId: bot.communityId, serverId: server.id }) + } + } + } + + async syncBotServerMembers({ + botId, + communityId, + serverId, + }: { + botId: string + communityId: string + serverId: string + }) { + const discordBot = this.ensureBotInstance(botId) + if (!discordBot) { + console.log(`Can't find bot.`, botId, serverId) + return false + } this.logger.verbose(`Fetching members... ${botId} ${serverId}`) const [discordIdentityIds, botMemberIds] = await Promise.all([ this.botMember.getDiscordIdentityIds(), this.botMember.getBotMemberIds(botId, serverId), ]) - const members = await bot.getDiscordServerMembers(serverId) + const members = await discordBot.getDiscordServerMembers(serverId) this.logger.verbose(`Found ${members.length} members to process`) const filtered = members @@ -202,20 +233,28 @@ export class ApiBotManagerService implements OnModuleInit { if (toBeDeleted.length) { this.logger.warn(`Found ${toBeDeleted.length} members to delete`) - this.logger.warn(`TODO: DELETE MEMBERS`) + for (const userId of toBeDeleted) { + this.logger.verbose(`Removing member ${userId} from bot ${botId} server ${serverId}...`) + await this.botMember.scheduleRemoveMember({ communityId, botId, serverId, userId }) + } } this.logger.verbose(`Found ${filtered.length} members to process (filtered)`) let linkedCount = 0 for (const member of filtered) { const userId = member.id - await this.botMember.scheduleAddMember(community.bot, serverId, userId) - - this.logger.verbose(`${botId} ${serverId} Processed ${member.user.username} (linked: ${!!member.id})`) + await this.botMember.scheduleAddMember({ communityId, botId: botId, serverId, userId }) if (member.id) { linkedCount++ } } + await this.core.logInfo( + `Found ${members.length} members to process (filtered ${filtered.length}) (linked ${linkedCount})`, + { + botId, + communityId, + }, + ) this.logger.verbose( `Found ${members.length} members to process (filtered ${filtered.length}) (linked ${linkedCount})`, ) diff --git a/libs/api/bot/data-access/src/lib/api-bot-member.service.ts b/libs/api/bot/data-access/src/lib/api-bot-member.service.ts index c849b645..e509427c 100644 --- a/libs/api/bot/data-access/src/lib/api-bot-member.service.ts +++ b/libs/api/bot/data-access/src/lib/api-bot-member.service.ts @@ -23,8 +23,22 @@ export class ApiBotMemberService { return } this.logger.verbose(`Setting up listeners for bot ${bot.name}`) - instance.client?.on('guildMemberAdd', (member) => this.scheduleAddMember(bot, member.guild.id, member.id)) - instance.client.on('guildMemberRemove', (member) => this.scheduleRemoveMember(bot, member.guild.id, member.id)) + instance.client?.on('guildMemberAdd', (member) => + this.scheduleAddMember({ + botId: bot.id, + communityId: bot.communityId, + serverId: member.guild.id, + userId: member.id, + }), + ) + instance.client.on('guildMemberRemove', (member) => + this.scheduleRemoveMember({ + botId: bot.id, + communityId: bot.communityId, + serverId: member.guild.id, + userId: member.id, + }), + ) } async upsert({ @@ -146,10 +160,20 @@ export class ApiBotMemberService { }) } - async scheduleAddMember(bot: Bot, serverId: string, userId: string) { - const jobId = `${bot.id}-${serverId}-${userId}` + async scheduleAddMember({ + botId, + communityId, + serverId, + userId, + }: { + botId: string + communityId: string + serverId: string + userId: string + }) { + const jobId = `${botId}-${serverId}-${userId}` await this.botMemberAddQueue - .add('member-add', { botId: bot.id, communityId: bot.communityId, serverId, userId }) + .add('member-add', { botId, communityId, serverId, userId }) .then((res) => { this.logger.verbose(`scheduleAddMember queued: ${res.id}`) }) @@ -157,10 +181,20 @@ export class ApiBotMemberService { this.logger.error(`scheduleAddMember error: ${jobId}: ${err}`) }) } - async scheduleRemoveMember(bot: Bot, serverId: string, userId: string) { - const jobId = `${bot.id}-${serverId}-${userId}` + async scheduleRemoveMember({ + botId, + communityId, + serverId, + userId, + }: { + botId: string + communityId: string + serverId: string + userId: string + }) { + const jobId = `${botId}-${serverId}-${userId}` await this.botMemberRemoveQueue - .add('member-remove', { botId: bot.id, communityId: bot.communityId, serverId, userId }) + .add('member-remove', { botId, communityId, serverId, userId }) .then((res) => { this.logger.verbose(`scheduleRemoveMember queued: ${res.id}`) }) diff --git a/libs/api/network-asset/data-access/src/lib/api-network-asset-sync.service.ts b/libs/api/network-asset/data-access/src/lib/api-network-asset-sync.service.ts index d2eee65d..ca915544 100644 --- a/libs/api/network-asset/data-access/src/lib/api-network-asset-sync.service.ts +++ b/libs/api/network-asset/data-access/src/lib/api-network-asset-sync.service.ts @@ -39,7 +39,7 @@ export class ApiNetworkAssetSyncService { return !!job.id } - @Cron(CronExpression.EVERY_MINUTE) + @Cron(CronExpression.EVERY_10_MINUTES) async syncAll() { const identities = await this.core.data.identity.findMany({ where: { diff --git a/libs/web/user/feature/src/lib/user-user-detail-feature.tsx b/libs/web/user/feature/src/lib/user-user-detail-feature.tsx index 0798407e..a22dd2ae 100644 --- a/libs/web/user/feature/src/lib/user-user-detail-feature.tsx +++ b/libs/web/user/feature/src/lib/user-user-detail-feature.tsx @@ -7,7 +7,17 @@ import { UserNetworkTokenFeature } from '@pubkey-link/web-network-token-feature' import { UiGrid } from '@pubkey-link/web-ui-core' import { useUserFineOneUser } from '@pubkey-link/web-user-data-access' import { UserUiProfile } from '@pubkey-link/web-user-ui' -import { UiCard, UiContainer, UiDebugModal, UiGroup, UiLoader, UiStack, UiTabRoutes, UiWarning } from '@pubkey-ui/core' +import { + UiCard, + UiContainer, + UiDebugModal, + UiGroup, + UiLoader, + UiStack, + UiTabRoutes, + UiTime, + UiWarning, +} from '@pubkey-ui/core' import { Link, useParams } from 'react-router-dom' export function UserUserDetailFeature() { @@ -43,13 +53,16 @@ export function UserUserDetailFeature() { /> {items?.map((identity) => ( - - - + + + {ellipsify(identity.name ?? identity.providerId, 6)} + {identity.syncEnded ? ( + + ) : null}