diff --git a/Server/api/servers.js b/Server/api/servers.js index 4776658..dd8f980 100644 --- a/Server/api/servers.js +++ b/Server/api/servers.js @@ -22,7 +22,7 @@ module.exports = (R, $) => { await next() }) - R.get('/api/server/:id', async ctx => { + R.get('/api/server/:id', async (ctx) => { const { userId } = ctx.session const { id } = ctx.params @@ -46,7 +46,7 @@ module.exports = (R, $) => { ctx.body = server }) - R.get('/api/server/:id/slug', async ctx => { + R.get('/api/server/:id/slug', async (ctx) => { const { id } = ctx.params const srv = await $.discord.getServer(id) @@ -60,7 +60,7 @@ module.exports = (R, $) => { ctx.body = srv }) - R.patch('/api/server/:id', async ctx => { + R.patch('/api/server/:id', async (ctx) => { const { userId } = ctx.session const { id } = ctx.params @@ -89,20 +89,19 @@ module.exports = (R, $) => { ...(categories != null ? { categories } : {}), }) - ctx.body = { ok: true } $.P.invalidate(id) $.discord.invalidate(id) }) - R.get('/api/admin/servers', async ctx => { + R.get('/api/admin/servers', async (ctx) => { const { userId } = ctx.session if (!$.discord.isRoot(userId)) { return } - ctx.body = $.discord.client.guilds.map(g => ({ + ctx.body = $.discord.client.guilds.map((g) => ({ url: `${process.env.APP_URL}/s/${g.id}`, name: g.name, members: g.members.array().length, @@ -110,7 +109,7 @@ module.exports = (R, $) => { })) }) - R.patch('/api/servers/:server/roles', async ctx => { + R.patch('/api/servers/:server/roles', async (ctx) => { const { userId } = ctx.session const { server } = ctx.params const { added, removed } = ctx.request.body @@ -126,21 +125,28 @@ module.exports = (R, $) => { // current roles and allowed roles are an inclusive set. // first, filter added and removed. - const sanitizedAdded = added.filter(role => allowedRoles.includes(role)) - const sanitizedRemoved = removed.filter(role => allowedRoles.includes(role)) + const sanitizedAdded = added.filter((role) => allowedRoles.includes(role)) + const sanitizedRemoved = removed.filter((role) => allowedRoles.includes(role)) // filter currentRoles by what's been removed (down is faster than up) - let newRoles = currentRoles.filter(role => !sanitizedRemoved.includes(role)) + let newRoles = currentRoles.filter((role) => !sanitizedRemoved.includes(role)) // last, add new roles newRoles = [...newRoles, ...sanitizedAdded] if (!arrayMatches(currentRoles, newRoles)) { - await $.discord.updateRoles(gm, newRoles) + if (process.env.FF_TransactionalRoles !== '0') { + await $.discord.updateRolesTx(gm, { + added: sanitizedAdded, + removed: sanitizedRemoved, + }) + } else { + await $.discord.updateRoles(gm, newRoles) + } } ctx.body = { ok: true } - + $.P.invalidate(userId) $.discord.invalidate(userId) }) @@ -160,4 +166,4 @@ const arrayMatches = (a, b) => { } return false -} \ No newline at end of file +} diff --git a/Server/services/discord.js b/Server/services/discord.js index 5eced43..0fa688c 100644 --- a/Server/services/discord.js +++ b/Server/services/discord.js @@ -1,6 +1,6 @@ const Service = require('./Service') const superagent = require('superagent') -const { DiscordClient, Member } = require('@roleypoly/rpc/discord') +const { DiscordClient, Member, RoleTransaction, TxDelta } = require('@roleypoly/rpc/discord') const { IDQuery, DiscordUser } = require('@roleypoly/rpc/shared') const { Empty } = require('google-protobuf/google/protobuf/empty_pb') const { NodeHttpTransport } = require('@improbable-eng/grpc-web-node-http-transport') @@ -34,7 +34,7 @@ class DiscordService extends Service { this.bootstrapRetries = 0 this.bootstrapRetriesMax = 10 - this.bootstrap().catch(e => { + this.bootstrap().catch((e) => { console.error(`bootstrap failure`, e) process.exit(-1) }) @@ -44,7 +44,7 @@ class DiscordService extends Service { try { const ownUser = await this.rpc.ownUser(new Empty(), this.sharedHeaders) this.ownUser = ownUser.toObject() - + const listGuilds = await this.rpc.listGuilds(new Empty(), this.sharedHeaders) this.syncGuilds(listGuilds.toObject().guildsList) } catch (e) { @@ -112,7 +112,7 @@ class DiscordService extends Service { q.setGuildid(serverId) const roles = await this.rpc.getGuildRoles(q, this.sharedHeaders) - return roles.toObject().rolesList.filter(role => role.id !== serverId) + return roles.toObject().rolesList.filter((role) => role.id !== serverId) }) } @@ -124,11 +124,11 @@ class DiscordService extends Service { } } - const matchFor = permissionInt => + const matchFor = (permissionInt) => !!gm.rolesList - .map(id => guildRoles.find(role => role.id === id)) - .filter(x => !!x) - .find(role => (role.permissions & permissionInt) === permissionInt) + .map((id) => guildRoles.find((role) => role.id === id)) + .filter((x) => !!x) + .find((role) => (role.permissions & permissionInt) === permissionInt) const isAdmin = guild.ownerid === gm.user.id || matchFor(0x00000008) const canManageRoles = isAdmin || matchFor(0x10000000) @@ -155,6 +155,34 @@ class DiscordService extends Service { await this.rpc.updateMember(member, this.sharedHeaders) } + async updateRolesTx(memberObj, { added, removed }) { + const roleTx = new RoleTransaction() + roleTx.setMember(this.memberToQueryProto(memberObj)) + + for (let toAdd of added) { + const delta = new TxDelta() + delta.setAction(TxDelta.Action.ADD) + delta.setRole(toAdd) + roleTx.addDelta(delta) + } + + for (let toRemove of removed) { + const delta = new TxDelta() + delta.setAction(TxDelta.Action.REMOVE) + delta.setRole(toRemove) + roleTx.addDelta(delta) + } + + await this.rpc.updateMemberRoles(roleTx, this.sharedHeaders) + } + + memberToQueryProto(member) { + const query = new IDQuery() + query.setGuildid(member.guildid) + query.setMemberid(member.user.id) + return query + } + memberToProto(member) { const memberProto = new Member() memberProto.setGuildid(member.guildid) @@ -224,7 +252,7 @@ class DiscordService extends Service { } async syncGuilds(guilds) { - guilds.forEach(guild => this.ctx.server.ensure(guild)) + guilds.forEach((guild) => this.ctx.server.ensure(guild)) } async cacheCurry(key, func) {