Skip to content

Commit 8a25de4

Browse files
committed
feat: limit access for community members
1 parent 8687909 commit 8a25de4

File tree

51 files changed

+354
-662
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+354
-662
lines changed

api-schema.graphql

+1-15
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ input AdminCreateCommunityInput {
2121
websiteUrl: String
2222
}
2323

24-
input AdminCreateCommunityMemberInput {
25-
communityId: String!
26-
role: CommunityRole!
27-
userId: String!
28-
}
29-
3024
input AdminCreateIdentityInput {
3125
ownerId: String!
3226
provider: IdentityProvider!
@@ -57,6 +51,7 @@ input AdminCreateUserInput {
5751
}
5852

5953
input AdminFindManyBotInput {
54+
communityId: String!
6055
limit: Int = 10
6156
page: Int = 1
6257
search: String
@@ -387,7 +382,6 @@ type Mutation {
387382
adminCreateBackup: Boolean!
388383
adminCreateBot(input: AdminCreateBotInput!): Bot
389384
adminCreateCommunity(input: AdminCreateCommunityInput!): Community
390-
adminCreateCommunityMember(input: AdminCreateCommunityMemberInput!): CommunityMember
391385
adminCreateIdentity(input: AdminCreateIdentityInput!): Identity
392386
adminCreateNetwork(input: AdminCreateNetworkInput!): Network
393387
adminCreateNetworkToken(input: AdminCreateNetworkTokenInput!): NetworkToken
@@ -419,15 +413,13 @@ type Mutation {
419413
register(input: RegisterInput!): User
420414
userCreateBot(input: UserCreateBotInput!): Bot
421415
userCreateCommunity(input: UserCreateCommunityInput!): Community
422-
userCreateCommunityMember(input: UserCreateCommunityMemberInput!): CommunityMember
423416
userCreateRule(input: UserCreateRuleInput!): Rule
424417
userCreateRuleCondition(input: UserCreateRuleConditionInput!): RuleCondition
425418
userCreateRulePermission(input: UserCreateRulePermissionInput!): RulePermission
426419
userDeleteBot(botId: String!): Boolean
427420
userDeleteCommunity(communityId: String!): Boolean
428421
userDeleteCommunityMember(communityMemberId: String!): Boolean
429422
userDeleteIdentity(identityId: String!): Boolean
430-
userDeleteLog(logId: String!): Boolean
431423
userDeleteRule(ruleId: String!): Boolean
432424
userDeleteRuleCondition(ruleConditionId: String!): Boolean
433425
userDeleteRulePermission(rulePermissionId: String!): Boolean
@@ -659,12 +651,6 @@ input UserCreateCommunityInput {
659651
websiteUrl: String
660652
}
661653

662-
input UserCreateCommunityMemberInput {
663-
communityId: String!
664-
role: CommunityRole!
665-
userId: String!
666-
}
667-
668654
input UserCreateRuleConditionInput {
669655
account: String
670656
amount: String

libs/api/bot/data-access/src/lib/api-bot-manager.service.ts

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Injectable, Logger, OnModuleInit } from '@nestjs/common'
2+
import { Bot } from '@prisma/client'
23

34
import { createDiscordRestClient, DiscordBot } from '@pubkey-link/api-bot-util'
45
import { ApiCoreService } from '@pubkey-link/api-core-data-access'
@@ -19,7 +20,7 @@ export class ApiBotManagerService implements OnModuleInit {
1920
const bots = await this.core.data.bot.findMany({ where: { status: BotStatus.Active } })
2021
for (const bot of bots) {
2122
this.logger.verbose(`Starting bot ${bot.name}`)
22-
await this.startBot(bot.id)
23+
await this.startBot(bot)
2324
}
2425
}
2526

@@ -41,15 +42,15 @@ export class ApiBotManagerService implements OnModuleInit {
4142
return `https://discord.com/developers/applications/${botId}`
4243
}
4344

44-
async getBotRoles(botId: string, serverId: string): Promise<DiscordRole[]> {
45+
async getBotRoles(userId: string, botId: string, serverId: string): Promise<DiscordRole[]> {
4546
const bot = this.getBotInstance(botId)
4647
if (!bot) {
4748
return []
4849
}
4950
return bot.getRoles(serverId)
5051
}
5152

52-
async getBotServers(botId: string): Promise<DiscordServer[]> {
53+
async getBotServers(userId: string, botId: string): Promise<DiscordServer[]> {
5354
const bot = this.getBotInstance(botId)
5455

5556
if (!bot) {
@@ -107,7 +108,7 @@ export class ApiBotManagerService implements OnModuleInit {
107108
return url.toString()
108109
}
109110

110-
async leaveBotServer(botId: string, serverId: string) {
111+
async leaveBotServer(userId: string, botId: string, serverId: string) {
111112
return this.ensureBotInstance(botId).leaveServer(serverId)
112113
}
113114

@@ -116,28 +117,32 @@ export class ApiBotManagerService implements OnModuleInit {
116117
// return `${this.core.config.apiUrl}/bot/${botId}/callback`
117118
}
118119

119-
async startBot(botId: string) {
120-
const bot = await this.core.data.bot.findUnique({ where: { id: botId } })
121-
if (!bot) {
122-
throw new Error(`Bot with id ${botId} not found`)
123-
}
120+
async userStartBot(userId: string, botId: string) {
121+
const bot = await this.botMember.ensureBotAdmin({ botId, userId })
122+
123+
return this.startBot(bot)
124+
}
125+
126+
async userStopBot(userId: string, botId: string) {
127+
const bot = await this.botMember.ensureBotAdmin({ botId, userId })
128+
129+
return this.stopBot(bot)
130+
}
131+
132+
private async startBot(bot: Bot) {
124133
if (this.bots.get(bot.id)) {
125134
throw new Error(`Bot ${bot.name} already started`)
126135
}
127136

128-
const instance = new DiscordBot({ botId, token: bot.token })
137+
const instance = new DiscordBot({ botId: bot.id, token: bot.token })
129138
await instance.start()
130139
await this.botMember.setupListeners(bot, instance)
131140
this.bots.set(bot.id, instance)
132141

133142
return true
134143
}
135144

136-
async stopBot(botId: string) {
137-
const bot = await this.core.data.bot.findUnique({ where: { id: botId } })
138-
if (!bot) {
139-
throw new Error(`Bot with id ${botId} not found`)
140-
}
145+
private async stopBot(bot: Bot) {
141146
const instance = this.bots.get(bot.id)
142147
if (!instance) {
143148
throw new Error(`Bot ${bot.name} not started`)
@@ -164,7 +169,7 @@ export class ApiBotManagerService implements OnModuleInit {
164169
return instance
165170
}
166171

167-
async syncBotServer(botId: string, serverId: string) {
172+
async syncBotServer(userId: string, botId: string, serverId: string) {
168173
const bot = this.ensureBotInstance(botId)
169174
if (!bot) {
170175
console.log(`Can't find bot.`, botId, serverId)

libs/api/bot/data-access/src/lib/api-bot-member.service.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ export class ApiBotMemberService {
120120
})
121121
}
122122

123+
async ensureBotAdmin({ botId, userId }: { botId: string; userId: string }) {
124+
const bot = await this.core.data.bot.findUnique({ where: { id: botId } })
125+
if (!bot) {
126+
throw new Error(`Bot with id ${botId} not found`)
127+
}
128+
await this.core.ensureCommunityAdmin({ userId, communityId: bot.communityId })
129+
return bot
130+
}
131+
123132
async getBotMemberIds(botId: string, serverId: string) {
124133
return this.core.data.botMember
125134
.findMany({ where: { botId, serverId } })
@@ -132,7 +141,7 @@ export class ApiBotMemberService {
132141
.then((items) => items.map((item) => item.providerId))
133142
}
134143

135-
async getBotMembers(botId: string, serverId: string) {
144+
async getBotMembers(userId: string, botId: string, serverId: string) {
136145
return this.core.data.botMember.findMany({
137146
where: {
138147
botId,
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,43 @@
11
import { Injectable, Logger } from '@nestjs/common'
22
import { ApiCoreService } from '@pubkey-link/api-core-data-access'
3-
import { User } from 'discord.js'
3+
import { User as DiscordUser } from 'discord.js'
4+
import { ApiBotManagerService } from './api-bot-manager.service'
5+
import { ApiBotMemberService } from './api-bot-member.service'
46
import { UserCreateBotInput } from './dto/user-create-bot.input'
57
import { UserUpdateBotInput } from './dto/user-update-bot.input'
6-
import { ApiBotManagerService } from './api-bot-manager.service'
78

89
@Injectable()
910
export class ApiUserBotService {
1011
private readonly logger = new Logger(ApiUserBotService.name)
11-
constructor(private readonly core: ApiCoreService, private readonly manager: ApiBotManagerService) {}
12-
13-
async createBot(input: UserCreateBotInput) {
14-
const user: User = await this.manager.getBotUser(input.token)
15-
this.logger.verbose(`Creating bot ${user.username}`)
16-
const avatarUrl = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=1024`
12+
constructor(
13+
private readonly core: ApiCoreService,
14+
private readonly manager: ApiBotManagerService,
15+
private readonly member: ApiBotMemberService,
16+
) {}
17+
18+
async createBot(userId: string, input: UserCreateBotInput) {
19+
await this.core.ensureCommunityAdmin({ communityId: input.communityId, userId })
20+
const bot: DiscordUser = await this.manager.getBotUser(input.token)
21+
this.logger.verbose(`Creating bot ${bot.username}`)
22+
const avatarUrl = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png?size=1024`
1723
return this.core.data.bot.create({
1824
data: {
19-
id: user.id,
25+
id: bot.id,
2026
avatarUrl,
21-
name: user.username,
27+
name: bot.username,
2228
...input,
2329
},
2430
})
2531
}
2632

27-
async deleteBot(botId: string) {
33+
async deleteBot(userId: string, botId: string) {
34+
await this.member.ensureBotAdmin({ botId, userId })
2835
const deleted = await this.core.data.bot.delete({ where: { id: botId } })
2936
return !!deleted
3037
}
3138

32-
async findOneBot(communityId: string) {
39+
async findOneBot(userId: string, communityId: string) {
40+
await this.core.ensureCommunityAccess({ communityId, userId })
3341
const bot = await this.core.data.bot.findUnique({
3442
where: { communityId },
3543
include: { permissions: { include: { rules: { include: { rule: true } } } } },
@@ -48,7 +56,8 @@ export class ApiUserBotService {
4856
return { ...bot, application }
4957
}
5058

51-
async updateBot(botId: string, input: UserUpdateBotInput) {
59+
async updateBot(userId: string, botId: string, input: UserUpdateBotInput) {
60+
await this.member.ensureBotAdmin({ botId, userId })
5261
return this.core.data.bot.update({ where: { id: botId }, data: input })
5362
}
5463
}

libs/api/bot/data-access/src/lib/dto/admin-find-many-bot.input.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { PagingInput } from '@pubkey-link/api-core-data-access'
33

44
@InputType()
55
export class AdminFindManyBotInput extends PagingInput() {
6+
@Field()
7+
communityId!: string
68
@Field({ nullable: true })
79
search?: string
810
}

libs/api/bot/data-access/src/lib/helpers/get-admin-bot-where.input.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { Prisma } from '@prisma/client'
22
import { AdminFindManyBotInput } from '../dto/admin-find-many-bot.input'
33

44
export function getAdminBotWhereInput(input: AdminFindManyBotInput): Prisma.BotWhereInput {
5-
const where: Prisma.BotWhereInput = {}
5+
const where: Prisma.BotWhereInput = {
6+
communityId: input.communityId,
7+
}
68

79
if (input.search) {
810
where.OR = [
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { UseGuards } from '@nestjs/common'
22
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
3-
import { ApiAuthGraphQLUserGuard } from '@pubkey-link/api-auth-data-access'
3+
import { ApiAuthGraphQLUserGuard, CtxUserId } from '@pubkey-link/api-auth-data-access'
44
import {
55
ApiBotService,
66
Bot,
@@ -17,62 +17,62 @@ export class ApiUserBotResolver {
1717
constructor(private readonly service: ApiBotService) {}
1818

1919
@Mutation(() => Bot, { nullable: true })
20-
userCreateBot(@Args('input') input: UserCreateBotInput) {
21-
return this.service.user.createBot(input)
20+
userCreateBot(@CtxUserId() userId: string, @Args('input') input: UserCreateBotInput) {
21+
return this.service.user.createBot(userId, input)
2222
}
2323

2424
@Mutation(() => Boolean, { nullable: true })
25-
userDeleteBot(@Args('botId') botId: string) {
26-
return this.service.user.deleteBot(botId)
25+
userDeleteBot(@CtxUserId() userId: string, @Args('botId') botId: string) {
26+
return this.service.user.deleteBot(userId, botId)
2727
}
2828

2929
@Query(() => Bot, { nullable: true })
30-
userFindOneBot(@Args('communityId') communityId: string) {
31-
return this.service.user.findOneBot(communityId)
30+
userFindOneBot(@CtxUserId() userId: string, @Args('communityId') communityId: string) {
31+
return this.service.user.findOneBot(userId, communityId)
3232
}
3333

3434
@Mutation(() => Bot, { nullable: true })
35-
userUpdateBot(@Args('botId') botId: string, @Args('input') input: UserUpdateBotInput) {
36-
return this.service.user.updateBot(botId, input)
35+
userUpdateBot(@CtxUserId() userId: string, @Args('botId') botId: string, @Args('input') input: UserUpdateBotInput) {
36+
return this.service.user.updateBot(userId, botId, input)
3737
}
3838

3939
@Query(() => [DiscordServer], { nullable: true })
40-
userGetBotServers(@Args('botId') botId: string) {
41-
return this.service.manager.getBotServers(botId)
40+
userGetBotServers(@CtxUserId() userId: string, @Args('botId') botId: string) {
41+
return this.service.manager.getBotServers(userId, botId)
4242
}
4343

4444
@Query(() => DiscordServer, { nullable: true })
45-
userGetBotServer(@Args('botId') botId: string, @Args('serverId') serverId: string) {
45+
userGetBotServer(@CtxUserId() userId: string, @Args('botId') botId: string, @Args('serverId') serverId: string) {
4646
return this.service.manager.getBotServer(botId, serverId)
4747
}
4848

4949
@Query(() => [DiscordRole], { nullable: true })
50-
userGetBotRoles(@Args('botId') botId: string, @Args('serverId') serverId: string) {
51-
return this.service.manager.getBotRoles(botId, serverId)
50+
userGetBotRoles(@CtxUserId() userId: string, @Args('botId') botId: string, @Args('serverId') serverId: string) {
51+
return this.service.manager.getBotRoles(userId, botId, serverId)
5252
}
5353

5454
@Query(() => [BotMember], { nullable: true })
55-
userGetBotMembers(@Args('botId') botId: string, @Args('serverId') serverId: string) {
56-
return this.service.member.getBotMembers(botId, serverId)
55+
userGetBotMembers(@CtxUserId() userId: string, @Args('botId') botId: string, @Args('serverId') serverId: string) {
56+
return this.service.member.getBotMembers(userId, botId, serverId)
5757
}
5858

5959
@Mutation(() => Boolean, { nullable: true })
60-
userLeaveBotServer(@Args('botId') botId: string, @Args('serverId') serverId: string) {
61-
return this.service.manager.leaveBotServer(botId, serverId)
60+
userLeaveBotServer(@CtxUserId() userId: string, @Args('botId') botId: string, @Args('serverId') serverId: string) {
61+
return this.service.manager.leaveBotServer(userId, botId, serverId)
6262
}
6363

6464
@Mutation(() => Boolean, { nullable: true })
65-
userSyncBotServer(@Args('botId') botId: string, @Args('serverId') serverId: string) {
66-
return this.service.manager.syncBotServer(botId, serverId)
65+
userSyncBotServer(@CtxUserId() userId: string, @Args('botId') botId: string, @Args('serverId') serverId: string) {
66+
return this.service.manager.syncBotServer(userId, botId, serverId)
6767
}
6868

6969
@Mutation(() => Boolean, { nullable: true })
70-
userStartBot(@Args('botId') botId: string) {
71-
return this.service.manager.startBot(botId)
70+
userStartBot(@CtxUserId() userId: string, @Args('botId') botId: string) {
71+
return this.service.manager.userStartBot(userId, botId)
7272
}
7373

7474
@Mutation(() => Boolean, { nullable: true })
75-
userStopBot(@Args('botId') botId: string) {
76-
return this.service.manager.stopBot(botId)
75+
userStopBot(@CtxUserId() userId: string, @Args('botId') botId: string) {
76+
return this.service.manager.userStopBot(userId, botId)
7777
}
7878
}

libs/api/community-member/data-access/src/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
export * from './lib/api-community-member-data-access.module'
22
export * from './lib/api-community-member.service'
3-
export * from './lib/dto/admin-create-community-member.input'
43
export * from './lib/dto/admin-find-many-community-member.input'
54
export * from './lib/dto/admin-update-community-member.input'
6-
export * from './lib/dto/user-create-community-member.input'
75
export * from './lib/dto/user-find-many-community-member.input'
86
export * from './lib/dto/user-update-community-member.input'
97
export * from './lib/entity/community-member-paging.entity'

libs/api/community-member/data-access/src/lib/api-admin-community-member.service.ts

-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Injectable } from '@nestjs/common'
22
import { ApiCoreService } from '@pubkey-link/api-core-data-access'
3-
import { AdminCreateCommunityMemberInput } from './dto/admin-create-community-member.input'
43
import { AdminFindManyCommunityMemberInput } from './dto/admin-find-many-community-member.input'
54
import { AdminUpdateCommunityMemberInput } from './dto/admin-update-community-member.input'
65
import { CommunityMemberPaging } from './entity/community-member-paging.entity'
@@ -10,10 +9,6 @@ import { getAdminCommunityMemberWhereInput } from './helpers/get-admin-community
109
export class ApiAdminCommunityMemberService {
1110
constructor(private readonly core: ApiCoreService) {}
1211

13-
async createCommunityMember(input: AdminCreateCommunityMemberInput) {
14-
return this.core.data.communityMember.create({ data: input })
15-
}
16-
1712
async deleteCommunityMember(communityMemberId: string) {
1813
const deleted = await this.core.data.communityMember.delete({ where: { id: communityMemberId } })
1914
return !!deleted

0 commit comments

Comments
 (0)