v3/packages/api/handlers/update-roles.ts
Katalina 2ff6588030
Refactor node packages to yarn workspaces & ditch next.js for CRA. (#161)
* chore: restructure project into yarn workspaces, remove next

* fix tests, remove webapp from terraform

* remove more ui deployment bits

* remove pages, fix FUNDING.yml

* remove isomorphism

* remove next providers

* fix linting issues

* feat: start basis of new web ui system on CRA

* chore: move types to @roleypoly/types package

* chore: move src/common/utils to @roleypoly/misc-utils

* chore: remove roleypoly/ path remappers

* chore: renmove vercel config

* chore: re-add worker-types to api package

* chore: fix type linting scope for api

* fix(web): craco should include all of packages dir

* fix(ci): change api webpack path for wrangler

* chore: remove GAR actions from CI

* chore: update codeql job

* chore: test better github dar matcher in lint-staged
2021-03-12 18:04:49 -05:00

141 lines
4.1 KiB
TypeScript

import {
GuildData,
Member,
Role,
RoleSafety,
RoleTransaction,
RoleUpdate,
SessionData,
TransactionType,
} from '@roleypoly/types';
import { difference, groupBy, keyBy, union } from 'lodash';
import { AuthType, discordFetch, respond, withSession } from '../utils/api-tools';
import { botToken } from '../utils/config';
import {
getGuild,
getGuildData,
getGuildMemberRoles,
updateGuildMemberRoles,
} from '../utils/guild';
const notFound = () => respond({ error: 'guild not found' }, { status: 404 });
export const UpdateRoles = withSession(
({ guilds, user: { id: userID } }: SessionData) => async (request: Request) => {
const updateRequest = (await request.json()) as RoleUpdate;
const [, , guildID] = new URL(request.url).pathname.split('/');
if (!guildID) {
return respond({ error: 'guild ID missing from URL' }, { status: 400 });
}
if (updateRequest.transactions.length === 0) {
return respond(
{ error: 'must have as least one transaction' },
{ status: 400 }
);
}
const guildCheck = guilds.find((guild) => guild.id === guildID);
if (!guildCheck) {
return notFound();
}
const guild = await getGuild(guildID);
if (!guild) {
return notFound();
}
const guildMemberRoles = await getGuildMemberRoles(
{ serverID: guildID, userID },
{ skipCachePull: true }
);
if (!guildMemberRoles) {
return notFound();
}
const newRoles = calculateNewRoles({
currentRoles: guildMemberRoles,
guildRoles: guild.roles,
guildData: await getGuildData(guildID),
updateRequest,
});
const patchMemberRoles = await discordFetch<Member>(
`/guilds/${guildID}/members/${userID}`,
botToken,
AuthType.Bot,
{
method: 'PATCH',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
roles: newRoles,
}),
}
);
if (!patchMemberRoles) {
return respond({ error: 'discord rejected the request' }, { status: 500 });
}
const updatedMember: Member = {
roles: patchMemberRoles.roles,
};
await updateGuildMemberRoles(
{ serverID: guildID, userID },
patchMemberRoles.roles
);
return respond(updatedMember);
}
);
const calculateNewRoles = ({
currentRoles,
guildData,
guildRoles,
updateRequest,
}: {
currentRoles: string[];
guildRoles: Role[];
guildData: GuildData;
updateRequest: RoleUpdate;
}): string[] => {
const roleMap = keyBy(guildRoles, 'id');
// These roles were ones changed between knownState (role picker page load/cache) and current (fresh from discord).
// We could cause issues, so we'll re-add them later.
// const diffRoles = difference(updateRequest.knownState, currentRoles);
// Only these are safe
const allSafeRoles = guildData.categories.reduce<string[]>(
(categorizedRoles, category) =>
!category.hidden
? [
...categorizedRoles,
...category.roles.filter(
(roleID) => roleMap[roleID]?.safety === RoleSafety.Safe
),
]
: categorizedRoles,
[]
);
const safeTransactions = updateRequest.transactions.filter((tx: RoleTransaction) =>
allSafeRoles.includes(tx.id)
);
const changesByAction = groupBy(safeTransactions, 'action');
const rolesToAdd = (changesByAction[TransactionType.Add] ?? []).map((tx) => tx.id);
const rolesToRemove = (changesByAction[TransactionType.Remove] ?? []).map(
(tx) => tx.id
);
const final = union(difference(currentRoles, rolesToRemove), rolesToAdd);
return final;
};