Stability improvements + react + proper rate-limit

This commit is contained in:
Atridad Lahiji 2023-07-25 12:20:00 -06:00
parent 69fb13dd16
commit 3608f56f80
No known key found for this signature in database
GPG key ID: 7CB8245F56BC3880
9 changed files with 75 additions and 83 deletions

View file

@ -5,7 +5,7 @@ A scrum poker tool that helps agile teams plan their sprints in real-time.
## Stack ## Stack
- Front-end framework: Nextjs - Front-end framework: Nextjs
- Front-end library: Preact - Front-end library: React
- Rendering method: SSR SPA - Rendering method: SSR SPA
- Hosting: Vercel - Hosting: Vercel
- Real-time pub/sub: Ably - Real-time pub/sub: Ably

View file

@ -12,17 +12,6 @@ const config = {
images: { images: {
domains: ["avatars.githubusercontent.com", "lh3.googleusercontent.com"], domains: ["avatars.githubusercontent.com", "lh3.googleusercontent.com"],
}, },
webpack: (config, { dev, isServer }) => {
if (!dev && !isServer) {
Object.assign(config.resolve.alias, {
"react/jsx-runtime.js": "preact/compat/jsx-runtime",
react: "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react-dom": "preact/compat",
});
}
return config;
},
}; };
export default config; export default config;

View file

@ -23,14 +23,14 @@
"@trpc/server": "10.35.0", "@trpc/server": "10.35.0",
"@upstash/ratelimit": "^0.4.3", "@upstash/ratelimit": "^0.4.3",
"@upstash/redis": "^1.22.0", "@upstash/redis": "^1.22.0",
"ably": "^1.2.41", "ably": "^1.2.42",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"json2csv": "6.0.0-alpha.2", "json2csv": "6.0.0-alpha.2",
"next": "^13.4.12", "next": "^13.4.12",
"next-auth": "^4.22.3", "next-auth": "^4.22.3",
"postcss": "^8.4.27", "postcss": "^8.4.27",
"preact": "^10.16.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0",
"react-email": "^1.9.4", "react-email": "^1.9.4",
"react-icons": "^4.10.1", "react-icons": "^4.10.1",
"resend": "^0.17.1", "resend": "^0.17.1",
@ -39,7 +39,7 @@
"zod": "^3.21.4" "zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@types/eslint": "^8.44.0", "@types/eslint": "^8.44.1",
"@types/json2csv": "^5.0.3", "@types/json2csv": "^5.0.3",
"@types/node": "^20.4.4", "@types/node": "^20.4.4",
"@types/react": "^18.2.16", "@types/react": "^18.2.16",

30
pnpm-lock.yaml generated
View file

@ -39,8 +39,8 @@ dependencies:
specifier: ^1.22.0 specifier: ^1.22.0
version: 1.22.0 version: 1.22.0
ably: ably:
specifier: ^1.2.41 specifier: ^1.2.42
version: 1.2.41 version: 1.2.42
autoprefixer: autoprefixer:
specifier: ^10.4.14 specifier: ^10.4.14
version: 10.4.14(postcss@8.4.27) version: 10.4.14(postcss@8.4.27)
@ -56,12 +56,12 @@ dependencies:
postcss: postcss:
specifier: ^8.4.27 specifier: ^8.4.27
version: 8.4.27 version: 8.4.27
preact:
specifier: ^10.16.0
version: 10.16.0
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-email: react-email:
specifier: ^1.9.4 specifier: ^1.9.4
version: 1.9.4 version: 1.9.4
@ -83,8 +83,8 @@ dependencies:
devDependencies: devDependencies:
'@types/eslint': '@types/eslint':
specifier: ^8.44.0 specifier: ^8.44.1
version: 8.44.0 version: 8.44.1
'@types/json2csv': '@types/json2csv':
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3 version: 5.0.3
@ -132,7 +132,7 @@ packages:
react: '>=18.1.0' react: '>=18.1.0'
react-dom: '>=18.1.0' react-dom: '>=18.1.0'
dependencies: dependencies:
ably: 1.2.41 ably: 1.2.42
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
@ -1087,8 +1087,8 @@ packages:
'@types/responselike': 1.0.0 '@types/responselike': 1.0.0
dev: false dev: false
/@types/eslint@8.44.0: /@types/eslint@8.44.1:
resolution: {integrity: sha512-gsF+c/0XOguWgaOgvFs+xnnRqt9GwgTvIks36WpE6ueeI4KCEHHd8K/CKHqhOqrJKsYH8m27kRzQEvWXAwXUTw==} resolution: {integrity: sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==}
dependencies: dependencies:
'@types/estree': 1.0.1 '@types/estree': 1.0.1
'@types/json-schema': 7.0.12 '@types/json-schema': 7.0.12
@ -1382,8 +1382,8 @@ packages:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: false dev: false
/ably@1.2.41: /ably@1.2.42:
resolution: {integrity: sha512-TE3PjtrclXFjfIALQhuTd1bEL7EAlS28AGFLaILeXmecXnBQE37WoC2EKZJIX7jRX31bwTHxUouje3WBEnhNKQ==} resolution: {integrity: sha512-dUnza7cERLWaDa/2pLVXtU2PJoU5k/t6g9sQZI1dgWC5Vok39nE6tf/xH2Rat7PJs7pXl9hLpkg1AhS4Xwd2/w==}
engines: {node: '>=5.10.x'} engines: {node: '>=5.10.x'}
dependencies: dependencies:
'@ably/msgpack-js': 0.4.0 '@ably/msgpack-js': 0.4.0
@ -1686,7 +1686,7 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001517 caniuse-lite: 1.0.30001517
electron-to-chromium: 1.4.469 electron-to-chromium: 1.4.470
node-releases: 2.0.13 node-releases: 2.0.13
update-browserslist-db: 1.0.11(browserslist@4.21.9) update-browserslist-db: 1.0.11(browserslist@4.21.9)
dev: false dev: false
@ -2169,8 +2169,8 @@ packages:
semver: 7.5.4 semver: 7.5.4
dev: false dev: false
/electron-to-chromium@1.4.469: /electron-to-chromium@1.4.470:
resolution: {integrity: sha512-HRN9XQjElxJBrdDky5iiUUr3eDwXGTg6Cp4IV8MuNc8VqMkYSneSnIe6poFKx9PsNzkudCgaWCBVxwDqirwQWQ==} resolution: {integrity: sha512-zZM48Lmy2FKWgqyvsX9XK+J6FfP7aCDUFLmgooLJzA7v1agCs/sxSoBpTIwDLhmbhpx9yJIxj2INig/ncjJRqg==}
dev: false dev: false
/emoji-regex@9.2.2: /emoji-regex@9.2.2:

View file

@ -2,12 +2,16 @@ import Ably from "ably";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
import type { EventType } from "../utils/types"; import type { EventType } from "../utils/types";
export const publishToChannel = async ( const ablyRest = new Ably.Rest(env.ABLY_PRIVATE_KEY);
ablyInstance: Ably.Types.RealtimePromise,
export const publishToChannel = (
channel: string, channel: string,
event: EventType, event: EventType,
message: string message: string
) => { ) => {
const channelName = ablyInstance.channels.get(`${env.APP_ENV}-${channel}`); try {
await channelName.publish(event, message); ablyRest.channels.get(`${env.APP_ENV}-${channel}`).publish(event, message);
} catch (error) {
console.log(`❌❌❌ Failed to send message!`);
}
}; };

View file

@ -0,0 +1,25 @@
import Ably from "ably/promises";
import { NextApiRequest, NextApiResponse } from "next";
import { env } from "~/env.mjs";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions);
if (session) {
// Signed in
const client = new Ably.Realtime(env.ABLY_PRIVATE_KEY);
const tokenRequestData = await client.auth.createTokenRequest({
clientId: session.user.id,
});
res.status(200).json(tokenRequestData);
} else {
// Not Signed in
res.status(401);
}
}

View file

@ -27,8 +27,7 @@ export const roomRouter = createTRPCRouter({
await invalidateCache(`kv_roomcount_admin`); await invalidateCache(`kv_roomcount_admin`);
await invalidateCache(`kv_roomlist_${ctx.session.user.id}`); await invalidateCache(`kv_roomlist_${ctx.session.user.id}`);
await publishToChannel( publishToChannel(
ctx.ably,
`${ctx.session.user.id}`, `${ctx.session.user.id}`,
"ROOM_LIST_UPDATE", "ROOM_LIST_UPDATE",
"CREATE" "CREATE"
@ -202,12 +201,7 @@ export const roomRouter = createTRPCRouter({
}); });
if (newRoom) { if (newRoom) {
await publishToChannel( publishToChannel(`${newRoom.id}`, "ROOM_UPDATE", "UPDATE");
ctx.ably,
`${newRoom.id}`,
"ROOM_UPDATE",
"UPDATE"
);
} }
return !!newRoom; return !!newRoom;
@ -228,19 +222,13 @@ export const roomRouter = createTRPCRouter({
await invalidateCache(`kv_votecount_admin`); await invalidateCache(`kv_votecount_admin`);
await invalidateCache(`kv_roomlist_${ctx.session.user.id}`); await invalidateCache(`kv_roomlist_${ctx.session.user.id}`);
await publishToChannel( publishToChannel(
ctx.ably,
`${ctx.session.user.id}`, `${ctx.session.user.id}`,
"ROOM_LIST_UPDATE", "ROOM_LIST_UPDATE",
"DELETE" "DELETE"
); );
await publishToChannel( publishToChannel(`${deletedRoom.id}`, "ROOM_UPDATE", "DELETE");
ctx.ably,
`${deletedRoom.id}`,
"ROOM_UPDATE",
"DELETE"
);
} }
return !!deletedRoom; return !!deletedRoom;

View file

@ -100,12 +100,7 @@ export const voteRouter = createTRPCRouter({
await invalidateCache(`kv_votecount_admin`); await invalidateCache(`kv_votecount_admin`);
await invalidateCache(`kv_votes_${input.roomId}`); await invalidateCache(`kv_votes_${input.roomId}`);
await publishToChannel( publishToChannel(`${vote.roomId}`, "VOTE_UPDATE", "UPDATE");
ctx.ably,
`${vote.roomId}`,
"VOTE_UPDATE",
"UPDATE"
);
} }
return !!vote; return !!vote;

View file

@ -19,7 +19,6 @@ import { type Session } from "next-auth";
import { getServerAuthSession } from "~/server/auth"; import { getServerAuthSession } from "~/server/auth";
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
import Ably from "ably";
type CreateContextOptions = { type CreateContextOptions = {
session: Session | null; session: Session | null;
@ -38,7 +37,6 @@ type CreateContextOptions = {
const createInnerTRPCContext = (opts: CreateContextOptions) => { const createInnerTRPCContext = (opts: CreateContextOptions) => {
return { return {
session: opts.session, session: opts.session,
ably: new Ably.Realtime.Promise(env.ABLY_PRIVATE_KEY),
prisma, prisma,
}; };
}; };
@ -108,7 +106,6 @@ const enforceRouteProtection = t.middleware(async ({ ctx, next }) => {
throw new TRPCError({ code: "UNAUTHORIZED" }); throw new TRPCError({ code: "UNAUTHORIZED" });
} }
try {
const rateLimit = new Ratelimit({ const rateLimit = new Ratelimit({
redis: Redis.fromEnv(), redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow( limiter: Ratelimit.slidingWindow(
@ -121,6 +118,7 @@ const enforceRouteProtection = t.middleware(async ({ ctx, next }) => {
const { success } = await rateLimit.limit( const { success } = await rateLimit.limit(
`${env.APP_ENV}_${ctx.session.user.id}` `${env.APP_ENV}_${ctx.session.user.id}`
); );
console.log(success);
if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" }); if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
return next({ return next({
@ -128,13 +126,6 @@ const enforceRouteProtection = t.middleware(async ({ ctx, next }) => {
session: { ...ctx.session, user: ctx.session.user }, session: { ...ctx.session, user: ctx.session.user },
}, },
}); });
} catch {
return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
});
}
}); });
/** /**