chore: update codestyle due to prettier/rule updates

This commit is contained in:
41666 2021-06-30 08:02:25 -04:00
parent f632bfa6e5
commit 10e095656f
16 changed files with 298 additions and 292 deletions

View file

@ -3,8 +3,8 @@ FROM node:14 AS builder
# Create the user and group files that will be used in the running container to # Create the user and group files that will be used in the running container to
# run the process as an unprivileged user. # run the process as an unprivileged user.
RUN mkdir /user \ RUN mkdir /user \
&& echo 'nobody:x:65534:65534:nobody:/:' >/user/passwd \ && echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd \
&& echo 'nobody:x:65534:' >/user/group && echo 'nobody:x:65534:' > /user/group
# Set the working directory outside $GOPATH to enable the support for modules. # Set the working directory outside $GOPATH to enable the support for modules.
WORKDIR /src WORKDIR /src

View file

@ -10,41 +10,42 @@ import {
} from '../utils/responses'; } from '../utils/responses';
export const ClearGuildCache = withSession( export const ClearGuildCache = withSession(
(session) => async (request: Request): Promise<Response> => { (session) =>
const url = new URL(request.url); async (request: Request): Promise<Response> => {
const [, , guildID] = url.pathname.split('/'); const url = new URL(request.url);
if (!guildID) { const [, , guildID] = url.pathname.split('/');
return missingParameters(); if (!guildID) {
} return missingParameters();
}
const rateLimit = useGuildRateLimiter( const rateLimit = useGuildRateLimiter(
guildID, guildID,
GuildRateLimiterKey.cacheClear, GuildRateLimiterKey.cacheClear,
60 * 5 60 * 5
); // 5 minute RL TTL, 288 times per day. ); // 5 minute RL TTL, 288 times per day.
if (!isRoot(session.user.id)) { if (!isRoot(session.user.id)) {
const guild = session.guilds.find((guild) => guild.id === guildID); const guild = session.guilds.find((guild) => guild.id === guildID);
if (!guild) { if (!guild) {
return notFound();
}
if (
guild?.permissionLevel !== UserGuildPermissions.Manager &&
guild?.permissionLevel !== UserGuildPermissions.Admin
) {
return lowPermissions();
}
if (await rateLimit()) {
return rateLimited();
}
}
const result = await getGuild(guildID, { skipCachePull: true });
if (!result) {
return notFound(); return notFound();
} }
return ok();
if (
guild?.permissionLevel !== UserGuildPermissions.Manager &&
guild?.permissionLevel !== UserGuildPermissions.Admin
) {
return lowPermissions();
}
if (await rateLimit()) {
return rateLimited();
}
} }
const result = await getGuild(guildID, { skipCachePull: true });
if (!result) {
return notFound();
}
return ok();
}
); );

View file

@ -5,52 +5,53 @@ import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild';
const fail = () => respond({ error: 'guild not found' }, { status: 404 }); const fail = () => respond({ error: 'guild not found' }, { status: 404 });
export const GetPickerData = withSession( export const GetPickerData = withSession(
(session: SessionData) => async (request: Request): Promise<Response> => { (session: SessionData) =>
const url = new URL(request.url); async (request: Request): Promise<Response> => {
const [, , guildID] = url.pathname.split('/'); const url = new URL(request.url);
const [, , guildID] = url.pathname.split('/');
if (!guildID) { if (!guildID) {
return respond({ error: 'missing guild id' }, { status: 400 }); return respond({ error: 'missing guild id' }, { status: 400 });
}
const { id: userID } = session.user as DiscordUser;
const guilds = session.guilds as GuildSlug[];
// Save a Discord API request by checking if this user is a member by session first
const checkGuild = guilds.find((guild) => guild.id === guildID);
if (!checkGuild) {
return fail();
}
const guild = await getGuild(guildID, {
skipCachePull: url.searchParams.has('__no_cache'),
});
if (!guild) {
return fail();
}
const memberRolesP = getGuildMemberRoles({
serverID: guildID,
userID,
});
const guildDataP = getGuildData(guildID);
const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]);
if (!memberRoles) {
return fail();
}
const presentableGuild: PresentableGuild = {
id: guildID,
guild: checkGuild,
roles: guild.roles,
member: {
roles: memberRoles,
},
data: guildData,
};
return respond(presentableGuild);
} }
const { id: userID } = session.user as DiscordUser;
const guilds = session.guilds as GuildSlug[];
// Save a Discord API request by checking if this user is a member by session first
const checkGuild = guilds.find((guild) => guild.id === guildID);
if (!checkGuild) {
return fail();
}
const guild = await getGuild(guildID, {
skipCachePull: url.searchParams.has('__no_cache'),
});
if (!guild) {
return fail();
}
const memberRolesP = getGuildMemberRoles({
serverID: guildID,
userID,
});
const guildDataP = getGuildData(guildID);
const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]);
if (!memberRoles) {
return fail();
}
const presentableGuild: PresentableGuild = {
id: guildID,
guild: checkGuild,
roles: guild.roles,
member: {
roles: memberRoles,
},
data: guildData,
};
return respond(presentableGuild);
}
); );

View file

@ -17,60 +17,61 @@ import {
} from '../utils/responses'; } from '../utils/responses';
export const SyncFromLegacy = withSession( export const SyncFromLegacy = withSession(
(session) => async (request: Request): Promise<Response> => { (session) =>
const url = new URL(request.url); async (request: Request): Promise<Response> => {
const [, , guildID] = url.pathname.split('/'); const url = new URL(request.url);
if (!guildID) { const [, , guildID] = url.pathname.split('/');
return missingParameters(); if (!guildID) {
} return missingParameters();
}
const rateLimit = useGuildRateLimiter( const rateLimit = useGuildRateLimiter(
guildID, guildID,
GuildRateLimiterKey.legacyImport, GuildRateLimiterKey.legacyImport,
60 * 20 60 * 20
); // 20 minute RL TTL, 72 times per day. ); // 20 minute RL TTL, 72 times per day.
// Allow root users to trigger this too, just in case. // Allow root users to trigger this too, just in case.
if (!isRoot(session.user.id)) { if (!isRoot(session.user.id)) {
const guild = session.guilds.find((guild) => guild.id === guildID); const guild = session.guilds.find((guild) => guild.id === guildID);
if (!guild) { if (!guild) {
return notFound();
}
if (
guild?.permissionLevel !== UserGuildPermissions.Manager &&
guild?.permissionLevel !== UserGuildPermissions.Admin
) {
return lowPermissions();
}
if (await rateLimit()) {
return rateLimited();
}
}
const shouldForce = url.searchParams.get('force') === 'yes';
// Not using getGuildData as we want null feedback, not a zeroed out object.
const checkGuild = await GuildData.get<GuildDataT>(guildID);
// Don't force, and guild exists in our side, but LegacyGuild flag is set,
// fail this request.
if (
!shouldForce &&
checkGuild &&
(checkGuild.features & Features.LegacyGuild) === Features.LegacyGuild
) {
return conflict();
}
const legacyGuild = await fetchLegacyServer(guildID);
if (!legacyGuild) {
return notFound(); return notFound();
} }
if ( const newGuildData = transformLegacyGuild(legacyGuild);
guild?.permissionLevel !== UserGuildPermissions.Manager && await GuildData.put(guildID, newGuildData);
guild?.permissionLevel !== UserGuildPermissions.Admin
) {
return lowPermissions();
}
if (await rateLimit()) { return ok();
return rateLimited();
}
} }
const shouldForce = url.searchParams.get('force') === 'yes';
// Not using getGuildData as we want null feedback, not a zeroed out object.
const checkGuild = await GuildData.get<GuildDataT>(guildID);
// Don't force, and guild exists in our side, but LegacyGuild flag is set,
// fail this request.
if (
!shouldForce &&
checkGuild &&
(checkGuild.features & Features.LegacyGuild) === Features.LegacyGuild
) {
return conflict();
}
const legacyGuild = await fetchLegacyServer(guildID);
if (!legacyGuild) {
return notFound();
}
const newGuildData = transformLegacyGuild(legacyGuild);
await GuildData.put(guildID, newGuildData);
return ok();
}
); );

View file

@ -21,70 +21,71 @@ import {
const notFound = () => respond({ error: 'guild not found' }, { status: 404 }); const notFound = () => respond({ error: 'guild not found' }, { status: 404 });
export const UpdateRoles = withSession( export const UpdateRoles = withSession(
({ guilds, user: { id: userID } }: SessionData) => async (request: Request) => { ({ guilds, user: { id: userID } }: SessionData) =>
const updateRequest = (await request.json()) as RoleUpdate; async (request: Request) => {
const [, , guildID] = new URL(request.url).pathname.split('/'); const updateRequest = (await request.json()) as RoleUpdate;
const [, , guildID] = new URL(request.url).pathname.split('/');
if (!guildID) { if (!guildID) {
return respond({ error: 'guild ID missing from URL' }, { status: 400 }); 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) { if (updateRequest.transactions.length === 0) {
return respond({ error: 'discord rejected the request' }, { status: 500 }); 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 updatedMember: Member = {
roles: patchMemberRoles.roles,
};
await updateGuildMemberRoles({ serverID: guildID, userID }, patchMemberRoles.roles);
return respond(updatedMember);
}
); );
const calculateNewRoles = ({ const calculateNewRoles = ({

View file

@ -27,17 +27,19 @@ export const addCORS = (init: ResponseInit = {}) => ({
export const respond = (obj: Record<string, any>, init: ResponseInit = {}) => export const respond = (obj: Record<string, any>, init: ResponseInit = {}) =>
new Response(JSON.stringify(obj), addCORS(init)); new Response(JSON.stringify(obj), addCORS(init));
export const resolveFailures = ( export const resolveFailures =
handleWith: () => Response, (
handler: (request: Request) => Promise<Response> | Response handleWith: () => Response,
) => async (request: Request): Promise<Response> => { handler: (request: Request) => Promise<Response> | Response
try { ) =>
return handler(request); async (request: Request): Promise<Response> => {
} catch (e) { try {
console.error(e); return handler(request);
return handleWith() || respond({ error: 'internal server error' }, { status: 500 }); } catch (e) {
} console.error(e);
}; return handleWith() || respond({ error: 'internal server error' }, { status: 500 });
}
};
export const parsePermissions = ( export const parsePermissions = (
permissions: bigint, permissions: bigint,
@ -106,33 +108,35 @@ export const discordFetch = async <T>(
} }
}; };
export const cacheLayer = <Identity, Data>( export const cacheLayer =
kv: WrappedKVNamespace, <Identity, Data>(
keyFactory: (identity: Identity) => string, kv: WrappedKVNamespace,
missHandler: (identity: Identity) => Promise<Data | null>, keyFactory: (identity: Identity) => string,
ttlSeconds?: number missHandler: (identity: Identity) => Promise<Data | null>,
) => async ( ttlSeconds?: number
identity: Identity, ) =>
options: { skipCachePull?: boolean } = {} async (
): Promise<Data | null> => { identity: Identity,
const key = keyFactory(identity); options: { skipCachePull?: boolean } = {}
): Promise<Data | null> => {
const key = keyFactory(identity);
if (!options.skipCachePull) { if (!options.skipCachePull) {
const value = await kv.get<Data>(key); const value = await kv.get<Data>(key);
if (value) { if (value) {
return value; return value;
}
} }
}
const fallbackValue = await missHandler(identity); const fallbackValue = await missHandler(identity);
if (!fallbackValue) { if (!fallbackValue) {
return null; return null;
} }
await kv.put(key, fallbackValue, ttlSeconds); await kv.put(key, fallbackValue, ttlSeconds);
return fallbackValue; return fallbackValue;
}; };
const NotAuthenticated = (extra?: string) => const NotAuthenticated = (extra?: string) =>
respond( respond(
@ -142,21 +146,21 @@ const NotAuthenticated = (extra?: string) =>
{ status: 403 } { status: 403 }
); );
export const withSession = ( export const withSession =
wrappedHandler: (session: SessionData) => Handler (wrappedHandler: (session: SessionData) => Handler): Handler =>
): Handler => async (request: Request): Promise<Response> => { async (request: Request): Promise<Response> => {
const sessionID = getSessionID(request); const sessionID = getSessionID(request);
if (!sessionID) { if (!sessionID) {
return NotAuthenticated('missing authentication'); return NotAuthenticated('missing authentication');
} }
const session = await Sessions.get<SessionData>(sessionID.id); const session = await Sessions.get<SessionData>(sessionID.id);
if (!session) { if (!session) {
return NotAuthenticated('authentication expired or not found'); return NotAuthenticated('authentication expired or not found');
} }
return await wrappedHandler(session)(request); return await wrappedHandler(session)(request);
}; };
export const setupStateSession = async <T>(data: T): Promise<string> => { export const setupStateSession = async <T>(data: T): Promise<string> => {
const stateID = (await KSUID.random()).string; const stateID = (await KSUID.random()).string;

View file

@ -1,4 +1,4 @@
const self = (global as any) as Record<string, string>; const self = global as any as Record<string, string>;
const env = (key: string) => self[key] ?? ''; const env = (key: string) => self[key] ?? '';

View file

@ -53,11 +53,7 @@ class EmulatedKV implements KVNamespace {
this.data.delete(key); this.data.delete(key);
} }
async list(options?: { async list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{
keys: { name: string; expiration?: number; metadata?: unknown }[]; keys: { name: string; expiration?: number; metadata?: unknown }[];
list_complete: boolean; list_complete: boolean;
cursor: string; cursor: string;
@ -83,7 +79,7 @@ class EmulatedKV implements KVNamespace {
const kvOrLocal = (namespace: KVNamespace | null): KVNamespace => const kvOrLocal = (namespace: KVNamespace | null): KVNamespace =>
namespace || new EmulatedKV(); namespace || new EmulatedKV();
const self = (global as any) as Record<string, any>; const self = global as any as Record<string, any>;
export const Sessions = new WrappedKVNamespace(kvOrLocal(self.KV_SESSIONS ?? null)); export const Sessions = new WrappedKVNamespace(kvOrLocal(self.KV_SESSIONS ?? null));
export const GuildData = new WrappedKVNamespace(kvOrLocal(self.KV_GUILD_DATA ?? null)); export const GuildData = new WrappedKVNamespace(kvOrLocal(self.KV_GUILD_DATA ?? null));

View file

@ -1,15 +1,13 @@
import { WrappedKVNamespace } from './kv'; import { WrappedKVNamespace } from './kv';
export const useRateLimiter = ( export const useRateLimiter =
kv: WrappedKVNamespace, (kv: WrappedKVNamespace, key: string, timeoutSeconds: number) =>
key: string, async (): Promise<boolean> => {
timeoutSeconds: number const value = await kv.get<boolean>(key);
) => async (): Promise<boolean> => { if (value) {
const value = await kv.get<boolean>(key); return true;
if (value) { }
return true;
}
await kv.put(key, true, timeoutSeconds); await kv.put(key, true, timeoutSeconds);
return false; return false;
}; };

View file

@ -1,5 +1,5 @@
import { roleypolyTheme } from './theme';
import { mdxComponents } from '../atoms/typography/mdx'; import { mdxComponents } from '../atoms/typography/mdx';
import { roleypolyTheme } from './theme';
export const parameters = { export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' }, actions: { argTypesRegex: '^on[A-Z].*' },

View file

@ -10,9 +10,10 @@ type DynamicLogoProps = LogoFlagProps & {
}; };
export const DynamicLogomark = (props: Partial<DynamicLogoProps>) => { export const DynamicLogomark = (props: Partial<DynamicLogoProps>) => {
const variant = React.useMemo(() => getRelevantVariant(props.currentDate), [ const variant = React.useMemo(
props.currentDate, () => getRelevantVariant(props.currentDate),
]); [props.currentDate]
);
if (!variant) { if (!variant) {
return <Logomark {...props} />; return <Logomark {...props} />;
@ -35,9 +36,10 @@ export const DynamicLogomark = (props: Partial<DynamicLogoProps>) => {
}; };
export const DynamicLogotype = (props: Partial<DynamicLogoProps>) => { export const DynamicLogotype = (props: Partial<DynamicLogoProps>) => {
const variant = React.useMemo(() => getRelevantVariant(props.currentDate), [ const variant = React.useMemo(
props.currentDate, () => getRelevantVariant(props.currentDate),
]); [props.currentDate]
);
if (!variant) { if (!variant) {
return <Logotype {...props} />; return <Logotype {...props} />;

View file

@ -38,14 +38,14 @@ export const EditorCategory = (props: Props) => {
const [roleSearchPopoverActive, setRoleSearchPopoverActive] = React.useState(false); const [roleSearchPopoverActive, setRoleSearchPopoverActive] = React.useState(false);
const [roleSearchTerm, updateSearchTerm] = React.useState(''); const [roleSearchTerm, updateSearchTerm] = React.useState('');
const onUpdate = (key: keyof typeof props.category, pred?: (newValue: any) => any) => ( const onUpdate =
newValue: any (key: keyof typeof props.category, pred?: (newValue: any) => any) =>
) => { (newValue: any) => {
props.onChange({ props.onChange({
...props.category, ...props.category,
[key]: pred ? pred(newValue) : newValue, [key]: pred ? pred(newValue) : newValue,
}); });
}; };
const handleRoleSelect = (role: RoleType) => { const handleRoleSelect = (role: RoleType) => {
setRoleSearchPopoverActive(false); setRoleSearchPopoverActive(false);

View file

@ -1,12 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { FeatureFlag, FeatureFlagProvider, FeatureFlagsContext } from './FeatureFlags'; import { FeatureFlag, FeatureFlagProvider, FeatureFlagsContext } from './FeatureFlags';
export const FeatureFlagDecorator = (flags: FeatureFlag[]) => ( export const FeatureFlagDecorator =
storyFn: () => React.ReactNode (flags: FeatureFlag[]) => (storyFn: () => React.ReactNode) => {
) => { return (
return ( <FeatureFlagsContext.Provider value={new FeatureFlagProvider(flags)}>
<FeatureFlagsContext.Provider value={new FeatureFlagProvider(flags)}> {storyFn()}
{storyFn()} </FeatureFlagsContext.Provider>
</FeatureFlagsContext.Provider> );
); };
};

View file

@ -1,10 +1,13 @@
import * as React from 'react'; import * as React from 'react';
export const withContext = <T, K extends T>( export const withContext =
Context: React.Context<T>, <T, K extends T>(
Component: React.ComponentType<K> Context: React.Context<T>,
): React.FunctionComponent<K> => (props) => ( Component: React.ComponentType<K>
<Context.Consumer> ): React.FunctionComponent<K> =>
{(context) => <Component {...props} {...context} />} (props) =>
</Context.Consumer> (
); <Context.Consumer>
{(context) => <Component {...props} {...context} />}
</Context.Consumer>
);

View file

@ -47,9 +47,8 @@ export const useSessionContext = () => React.useContext(SessionContext);
export const SessionContextProvider = (props: { children: React.ReactNode }) => { export const SessionContextProvider = (props: { children: React.ReactNode }) => {
const { fetch } = useApiContext(); const { fetch } = useApiContext();
const [sessionID, setSessionID] = React.useState<SessionContextT['sessionID']>( const [sessionID, setSessionID] =
undefined React.useState<SessionContextT['sessionID']>(undefined);
);
const [sessionState, setSessionState] = React.useState<SessionState>( const [sessionState, setSessionState] = React.useState<SessionState>(
SessionState.NoAuth SessionState.NoAuth
); );

View file

@ -41,9 +41,10 @@ const Picker = (props: PickerProps) => {
fetchPickerData(); fetchPickerData();
}, [props.serverID, authedFetch, pushRecentGuild]); }, [props.serverID, authedFetch, pushRecentGuild]);
React.useCallback((serverID) => pushRecentGuild(serverID), [pushRecentGuild])( React.useCallback(
props.serverID (serverID) => pushRecentGuild(serverID),
); [pushRecentGuild]
)(props.serverID);
if (!isAuthenticated) { if (!isAuthenticated) {
return <Redirect to={`/auth/login?r=${props.serverID}`} replace />; return <Redirect to={`/auth/login?r=${props.serverID}`} replace />;