update web, fix integration issues

This commit is contained in:
41666 2022-01-30 16:14:52 -05:00
parent 2fb721078e
commit e162096c03
30 changed files with 476 additions and 2574 deletions

View file

@ -46,7 +46,7 @@ describe('getGuild', () => {
roles: [],
};
await config.kv.guilds.put('guilds/123', guild, config.retention.guild);
await config.kv.guilds.put('123', guild, config.retention.guild);
mockDiscordFetch.mockReturnValue({ ...guild, name: 'test2' });
const result = await getGuild(config, '123');
@ -220,7 +220,7 @@ describe('getGuildMember', () => {
nick: 'test2',
};
await config.kv.guilds.put('guilds/123/members/123', member, config.retention.guild);
await config.kv.guilds.put('123:members:123', member, config.retention.guild);
mockDiscordFetch.mockReturnValue({ ...member, nick: 'test' });
const result = await getGuildMember(config, '123', '123');

View file

@ -25,7 +25,7 @@ export const getGuild = async (
forceMiss?: boolean
): Promise<(Guild & OwnRoleInfo) | null> =>
config.kv.guilds.cacheThrough(
`guilds/${id}`,
`guild/${id}`,
async () => {
const guildRaw = await discordFetch<APIGuild>(
`/guilds/${id}`,
@ -54,7 +54,7 @@ export const getGuild = async (
managed: role.managed,
position: role.position,
permissions: role.permissions,
safety: RoleSafety.Safe, // TODO: calculate this
safety: calculateRoleSafety(role, highestRolePosition),
}));
const guild: Guild & OwnRoleInfo = {
@ -133,7 +133,7 @@ export const getGuildMember = async (
overrideRetention?: number // allows for own-member to be cached as long as it's used.
): Promise<Member | null> =>
config.kv.guilds.cacheThrough(
`guilds/${serverID}/members/${userID}`,
`members/${serverID}/${userID}`,
async () => {
const discordMember = await discordFetch<APIMember>(
`/guilds/${serverID}/members/${userID}`,
@ -156,6 +156,23 @@ export const getGuildMember = async (
forceMiss
);
export const updateGuildMember = async (
config: Config,
serverID: string,
member: APIMember
): Promise<void> => {
config.kv.guilds.put(
`members/${serverID}/${member.user.id}`,
{
guildid: serverID,
roles: member.roles,
pending: member.pending,
nick: member.nick,
},
config.retention.member
);
};
const calculateRoleSafety = (role: Role | APIRole, highestBotRolePosition: number) => {
let safety = RoleSafety.Safe;

View file

@ -19,7 +19,7 @@ import { Router } from 'itty-router';
import { authBounce } from './routes/auth/bounce';
import { Environment, parseEnvironment } from './utils/config';
import { Context, RoleypolyHandler } from './utils/context';
import { json, notFound, serverError } from './utils/response';
import { corsHeaders, json, notFound, serverError } from './utils/response';
const router = Router();
@ -42,8 +42,7 @@ router.delete(
);
router.put('/guilds/:guildId/roles', ...guildsCommon, guildsRolesPut);
// Slug is unauthenticated...
router.get('/guilds/slug/:guildId', injectParams, guildsSlug);
router.get('/guilds/:guildId/slug', injectParams, withSession, guildsSlug);
router.post('/interactions', handleInteraction);
@ -60,7 +59,23 @@ router.get('/', ((request: Request, { config }: Context) =>
meta: config.uiPublicURI,
})) as RoleypolyHandler);
router.any('*', () => notFound());
router.options('*', (request: Request) => {
return new Response(null, {
headers: {
...corsHeaders,
},
});
});
router.all('/*', notFound);
const scrubURL = (urlStr: string) => {
const url = new URL(urlStr);
url.searchParams.delete('code');
url.searchParams.delete('state');
return url.toString();
};
export default {
async fetch(request: Request, env: Environment, event: Context['fetchContext']) {
@ -68,13 +83,14 @@ export default {
const context: Context = {
config,
fetchContext: {
waitUntil: event.waitUntil,
waitUntil: event.waitUntil.bind(event),
},
authMode: {
type: 'anonymous',
},
params: {},
};
console.log(`${request.method} ${scrubURL(request.url)}`);
return router
.handle(request, context)
.catch((e: Error) => (!e ? notFound() : serverError(e)));

View file

@ -2,7 +2,7 @@ import { isAllowedCallbackHost } from '@roleypoly/api/src/routes/auth/bounce';
import { createSession } from '@roleypoly/api/src/sessions/create';
import { getStateSession } from '@roleypoly/api/src/sessions/state';
import { Context, RoleypolyHandler } from '@roleypoly/api/src/utils/context';
import { AuthType, discordAPIBase, discordFetch } from '@roleypoly/api/src/utils/discord';
import { AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
import { dateFromID } from '@roleypoly/api/src/utils/id';
import { formDataRequest, getQuery } from '@roleypoly/api/src/utils/request';
import { seeOther } from '@roleypoly/api/src/utils/response';
@ -51,7 +51,7 @@ export const authCallback: RoleypolyHandler = async (
}
const response = await discordFetch<AuthTokenResponse>(
`${discordAPIBase}/oauth2/token`,
`/oauth2/token`,
'',
AuthType.None,
formDataRequest({

View file

@ -2,9 +2,10 @@ import {
getGuild,
getGuildData,
getGuildMember,
updateGuildMember,
} from '@roleypoly/api/src/guilds/getters';
import { Context, RoleypolyHandler } from '@roleypoly/api/src/utils/context';
import { AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
import { APIMember, AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
import {
engineeringProblem,
invalid,
@ -66,11 +67,14 @@ export const guildsRolesPut: RoleypolyHandler = async (
updateRequest,
});
if (isIdenticalArray(member.roles, newRoles)) {
if (
isIdenticalArray(member.roles, newRoles) ||
isIdenticalArray(updateRequest.knownState, newRoles)
) {
return invalid();
}
const patchMemberRoles = await discordFetch<Member>(
const patchMemberRoles = await discordFetch<APIMember>(
`/guilds/${guildID}/members/${userID}`,
context.config.botToken,
AuthType.Bot,
@ -90,7 +94,9 @@ export const guildsRolesPut: RoleypolyHandler = async (
return serverError(new Error('discord rejected the request'));
}
context.fetchContext.waitUntil(getGuildMember(context.config, guildID, userID, true));
context.fetchContext.waitUntil(
updateGuildMember(context.config, guildID, patchMemberRoles)
);
const updatedMember: Member = {
roles: patchMemberRoles.roles,

View file

@ -4,6 +4,7 @@ import {
getGuildMember,
} from '@roleypoly/api/src/guilds/getters';
import { Context, RoleypolyHandler } from '@roleypoly/api/src/utils/context';
import { getQuery } from '@roleypoly/api/src/utils/request';
import { json, notFound } from '@roleypoly/api/src/utils/response';
import { PresentableGuild } from '@roleypoly/types';
@ -11,7 +12,8 @@ export const guildsGuild: RoleypolyHandler = async (
request: Request,
context: Context
) => {
const guild = await getGuild(context.config, context.params!.guildId!);
const { noCache } = getQuery(request);
const guild = await getGuild(context.config, context.params!.guildId!, !!noCache);
if (!guild) {
return notFound();
@ -20,7 +22,8 @@ export const guildsGuild: RoleypolyHandler = async (
const member = await getGuildMember(
context.config,
context.params!.guildId!,
context.session!.user.id
context.session!.user.id,
!!noCache
);
if (!member) {

View file

@ -8,6 +8,13 @@ export const guildsSlug: RoleypolyHandler = async (
context: Context
) => {
const id = context.params.guildId!;
const guildInSession = context.session?.guilds.find((guild) => guild.id === id);
if (guildInSession) {
return json<GuildSlug>(guildInSession);
}
const guild = await getGuild(context.config, id);
if (!guild) {
return notFound();
@ -19,5 +26,6 @@ export const guildsSlug: RoleypolyHandler = async (
icon: guild.icon,
permissionLevel: UserGuildPermissions.User,
};
return json(slug);
return json<GuildSlug>(slug);
};

View file

@ -125,6 +125,9 @@ export type APIMember = {
roles: string[];
pending: boolean;
nick: string;
user: {
id: string;
};
};
export const parsePermissions = (

View file

@ -22,7 +22,7 @@ export const fetchLegacyServer = async (
config: Config,
id: string
): Promise<LegacyGuildData | null> => {
if (!config.interactionsSharedKey) {
if (!config.importSharedKey) {
return null;
}

View file

@ -1,14 +1,22 @@
export const json = (obj: any, init?: ResponseInit): Response => {
export const json = <T>(obj: T, init?: ResponseInit): Response => {
const body = JSON.stringify(obj);
return new Response(body, {
...init,
headers: {
...init?.headers,
'content-type': 'application/json; charset=utf-8',
...corsHeaders,
},
});
};
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS, PATCH',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
export const noContent = () => new Response(null, { status: 204 });
export const seeOther = (url: string) =>
new Response(