mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-29 18:26:34 +02:00
♻️ Add abstractions for tracking server-side events (#1143)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
parent
81d2f2c0bd
commit
d43bc631f1
9 changed files with 69 additions and 12 deletions
|
@ -36,6 +36,7 @@
|
|||
"@trpc/client": "^10.13.0",
|
||||
"@trpc/next": "^10.13.0",
|
||||
"@trpc/react-query": "^10.13.0",
|
||||
"@vercel/functions": "^1.0.2",
|
||||
"accept-language-parser": "^1.5.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { waitUntil } from "@vercel/functions";
|
||||
import { PostHog } from "posthog-node";
|
||||
|
||||
export function PostHogClient() {
|
||||
function PostHogClient() {
|
||||
if (!process.env.NEXT_PUBLIC_POSTHOG_API_KEY) return null;
|
||||
|
||||
const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, {
|
||||
|
@ -10,3 +11,13 @@ export function PostHogClient() {
|
|||
});
|
||||
return posthogClient;
|
||||
}
|
||||
|
||||
export const posthog = PostHogClient();
|
||||
|
||||
export function posthogApiHandler() {
|
||||
try {
|
||||
waitUntil(Promise.all([posthog?.shutdownAsync()]));
|
||||
} catch (error) {
|
||||
console.error("Failed to flush PostHog events:", error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { posthogApiHandler } from "@/app/posthog";
|
||||
import { AuthApiRoute } from "@/utils/auth";
|
||||
import { composeApiHandlers } from "@/utils/next";
|
||||
|
||||
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "HEAD") {
|
||||
return res.status(200).end();
|
||||
res.status(200).end();
|
||||
res.setHeader('Content-Length', '0');
|
||||
} else {
|
||||
return composeApiHandlers(AuthApiRoute, posthogApiHandler)(req, res);
|
||||
}
|
||||
|
||||
return AuthApiRoute(req, res);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ import { AppRouter, appRouter } from "@rallly/backend/trpc/routers";
|
|||
import * as Sentry from "@sentry/nextjs";
|
||||
import { createNextApiHandler } from "@trpc/server/adapters/next";
|
||||
|
||||
import { posthog, posthogApiHandler } from "@/app/posthog";
|
||||
import { absoluteUrl, shortUrl } from "@/utils/absolute-url";
|
||||
import { getServerSession, isEmailBlocked } from "@/utils/auth";
|
||||
import { isSelfHosted } from "@/utils/constants";
|
||||
import { emailClient } from "@/utils/emails";
|
||||
import { composeApiHandlers } from "@/utils/next";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
@ -14,10 +16,10 @@ export const config = {
|
|||
},
|
||||
};
|
||||
|
||||
export default createNextApiHandler<AppRouter>({
|
||||
const trpcApiHandler = createNextApiHandler<AppRouter>({
|
||||
router: appRouter,
|
||||
createContext: async (opts) => {
|
||||
return createTRPCContext(opts, {
|
||||
const res = createTRPCContext(opts, {
|
||||
async getUser({ req, res }) {
|
||||
const session = await getServerSession(req, res);
|
||||
|
||||
|
@ -30,12 +32,15 @@ export default createNextApiHandler<AppRouter>({
|
|||
isGuest: session.user.email === null,
|
||||
};
|
||||
},
|
||||
posthogClient: posthog || undefined,
|
||||
emailClient,
|
||||
isSelfHosted,
|
||||
isEmailBlocked,
|
||||
absoluteUrl,
|
||||
shortUrl,
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
onError({ error }) {
|
||||
if (error.code === "INTERNAL_SERVER_ERROR") {
|
||||
|
@ -43,3 +48,5 @@ export default createNextApiHandler<AppRouter>({
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default composeApiHandlers(trpcApiHandler, posthogApiHandler);
|
||||
|
|
|
@ -17,7 +17,7 @@ import EmailProvider from "next-auth/providers/email";
|
|||
import GoogleProvider from "next-auth/providers/google";
|
||||
import { Provider } from "next-auth/providers/index";
|
||||
|
||||
import { PostHogClient } from "@/app/posthog";
|
||||
import { posthog } from "@/app/posthog";
|
||||
import { absoluteUrl } from "@/utils/absolute-url";
|
||||
import { CustomPrismaAdapter } from "@/utils/auth/custom-prisma-adapter";
|
||||
import { mergeGuestsIntoUser } from "@/utils/auth/merge-user";
|
||||
|
@ -176,7 +176,6 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
|||
},
|
||||
callbacks: {
|
||||
async signIn({ user, email, account, profile }) {
|
||||
const posthog = PostHogClient();
|
||||
const distinctId = user.email ?? user.id;
|
||||
// prevent sign in if email is not verified
|
||||
if (
|
||||
|
@ -191,7 +190,6 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
|||
reason: "email not verified",
|
||||
},
|
||||
});
|
||||
await posthog?.shutdownAsync();
|
||||
return false;
|
||||
}
|
||||
// Make sure email is allowed
|
||||
|
@ -224,6 +222,14 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
|||
await mergeGuestsIntoUser(user.id, [session.user.id]);
|
||||
}
|
||||
|
||||
posthog?.identify({
|
||||
distinctId,
|
||||
properties: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
|
||||
posthog?.capture({
|
||||
distinctId,
|
||||
event: "login",
|
||||
|
@ -231,7 +237,6 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
|||
method: account?.provider,
|
||||
},
|
||||
});
|
||||
await posthog?.shutdownAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
12
apps/web/src/utils/next.ts
Normal file
12
apps/web/src/utils/next.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { NextApiHandler } from "next";
|
||||
|
||||
export function composeApiHandlers(...fns: NextApiHandler[]): NextApiHandler {
|
||||
return async (req, res) => {
|
||||
for (const fn of fns) {
|
||||
await fn(req, res);
|
||||
if (res.writableEnded) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -12,7 +12,6 @@
|
|||
"@rallly/database": "*",
|
||||
"@rallly/emails": "*",
|
||||
"@rallly/utils": "*",
|
||||
"@vercel/functions": "^1.0.2",
|
||||
"@trpc/server": "^10.13.0",
|
||||
"iron-session": "^6.3.1",
|
||||
"spacetime": "^7.4.7",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { EmailClient } from "@rallly/emails";
|
||||
import { inferAsyncReturnType, TRPCError } from "@trpc/server";
|
||||
import { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
import type { PostHog } from "posthog-node";
|
||||
|
||||
export type GetUserFn = (opts: CreateNextContextOptions) => Promise<{
|
||||
id: string;
|
||||
|
@ -12,6 +13,7 @@ export interface TRPCContextParams {
|
|||
emailClient: EmailClient;
|
||||
isSelfHosted: boolean;
|
||||
isEmailBlocked?: (email: string) => boolean;
|
||||
posthogClient?: PostHog;
|
||||
/**
|
||||
* Takes a relative path and returns an absolute URL to the app
|
||||
* @param path
|
||||
|
|
|
@ -626,6 +626,7 @@ export const polls = router({
|
|||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
timeZone: true,
|
||||
title: true,
|
||||
location: true,
|
||||
|
@ -859,7 +860,23 @@ export const polls = router({
|
|||
});
|
||||
});
|
||||
|
||||
waitUntil(Promise.all([emailToHost, ...emailsToParticipants]));
|
||||
ctx.posthogClient?.capture({
|
||||
distinctId: ctx.user.id,
|
||||
event: "finalize poll",
|
||||
properties: {
|
||||
number_of_participants: poll.participants.length,
|
||||
number_of_attendees: attendees.length,
|
||||
dayjs_since_created: dayjs().diff(poll.createdAt, "day"),
|
||||
},
|
||||
});
|
||||
|
||||
waitUntil(
|
||||
Promise.all([
|
||||
emailToHost,
|
||||
...emailsToParticipants,
|
||||
ctx.posthogClient?.flushAsync(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}),
|
||||
reopen: possiblyPublicProcedure
|
||||
|
|
Loading…
Add table
Reference in a new issue