make non-core env variables optional

This commit is contained in:
MarconLP 2023-05-03 21:35:58 +02:00
parent e05b05f673
commit 29f2da7379
No known key found for this signature in database
GPG key ID: F4CAFFDFA3451D5E
13 changed files with 128 additions and 102 deletions

View file

@ -1,15 +1,9 @@
# Prisma # Database
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DATABASE_URL="" DATABASE_URL=""
# Next Auth # Authjs
# You can generate a new secret on the command line with: NEXTAUTH_URL="http://localhost:3000/"
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
NEXTAUTH_SECRET="" NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"
# Next Auth Provider
GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET="" GOOGLE_CLIENT_SECRET=""
GITHUB_ID="" GITHUB_ID=""
@ -38,10 +32,10 @@ NEXT_PUBLIC_CRISP_WEBSITE_ID=""
# posthog # posthog
NEXT_PUBLIC_POSTHOG_KEY="" NEXT_PUBLIC_POSTHOG_KEY=""
NEXT_PUBLIC_POSTHOG_HOST="" NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com"
NEXT_PUBLIC_POSTHOG_PROXY_HOST="/ioafe" NEXT_PUBLIC_POSTHOG_PROXY_HOST="/ioafe"
POSTHOG_PROXY_PATH="ioafe" POSTHOG_PROXY_PATH="ioafe"
# redis for ratelimiting # redis for ratelimiting
UPSTASH_REDIS_REST_URL="" UPSTASH_REDIS_REST_URL=""
UPSTASH_REDIS_REST_TOKEN="=" UPSTASH_REDIS_REST_TOKEN=""

View file

@ -4,7 +4,9 @@ import { env } from "~/env.mjs";
export default function CrispChat() { export default function CrispChat() {
useEffect(() => { useEffect(() => {
if (env.NEXT_PUBLIC_CRISP_WEBSITE_ID) {
Crisp.configure(env.NEXT_PUBLIC_CRISP_WEBSITE_ID); Crisp.configure(env.NEXT_PUBLIC_CRISP_WEBSITE_ID);
}
}, []); }, []);
return null; return null;

View file

@ -28,13 +28,13 @@ const server = z.object({
AWS_KEY_ID: z.string(), AWS_KEY_ID: z.string(),
AWS_SECRET_ACCESS_KEY: z.string(), AWS_SECRET_ACCESS_KEY: z.string(),
AWS_BUCKET_NAME: z.string(), AWS_BUCKET_NAME: z.string(),
STRIPE_SECRET_KEY: z.string(), STRIPE_SECRET_KEY: z.string().nullish(),
STRIPE_WEBHOOK_SECRET: z.string(), STRIPE_WEBHOOK_SECRET: z.string().nullish(),
STRIPE_MONTHLY_PRICE_ID: z.string(), STRIPE_MONTHLY_PRICE_ID: z.string().nullish(),
STRIPE_ANNUAL_PRICE_ID: z.string(), STRIPE_ANNUAL_PRICE_ID: z.string().nullish(),
POSTHOG_PROXY_PATH: z.string(), POSTHOG_PROXY_PATH: z.string(),
UPSTASH_REDIS_REST_URL: z.string(), UPSTASH_REDIS_REST_URL: z.string().nullish(),
UPSTASH_REDIS_REST_TOKEN: z.string(), UPSTASH_REDIS_REST_TOKEN: z.string().nullish(),
}); });
/** /**
@ -43,9 +43,9 @@ const server = z.object({
*/ */
const client = z.object({ const client = z.object({
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1), // NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string(), NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().nullish(),
NEXT_PUBLIC_CRISP_WEBSITE_ID: z.string(), NEXT_PUBLIC_CRISP_WEBSITE_ID: z.string().nullish(),
NEXT_PUBLIC_POSTHOG_KEY: z.string(), NEXT_PUBLIC_POSTHOG_KEY: z.string().nullish(),
NEXT_PUBLIC_POSTHOG_HOST: z.string(), NEXT_PUBLIC_POSTHOG_HOST: z.string(),
NEXT_PUBLIC_POSTHOG_PROXY_HOST: z.string(), NEXT_PUBLIC_POSTHOG_PROXY_HOST: z.string(),
}); });

View file

@ -12,12 +12,12 @@ import { env } from "~/env.mjs";
import { type ReactNode, useEffect } from "react"; import { type ReactNode, useEffect } from "react";
// Check that PostHog is client-side (used to handle Next.js SSR) // Check that PostHog is client-side (used to handle Next.js SSR)
if (typeof window !== "undefined") { if (typeof window !== "undefined" && !!env.NEXT_PUBLIC_POSTHOG_KEY) {
posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, { posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: env.NEXT_PUBLIC_POSTHOG_PROXY_HOST, api_host: env.NEXT_PUBLIC_POSTHOG_PROXY_HOST,
// Enable debug mode in development // Enable debug mode in development
loaded: (posthog) => { loaded: (posthog) => {
if (process.env.NODE_ENV === "development") posthog.debug(false); if (process.env.NODE_ENV === "development") posthog.debug();
}, },
}); });
} }
@ -47,16 +47,16 @@ const PostHogIdentificationWrapper = ({
const posthog = usePostHog(); const posthog = usePostHog();
useEffect(() => { useEffect(() => {
if (!posthog) return; if (!posthog?.__loaded) return;
if (status === "authenticated") { if (status === "authenticated") {
const { id, name, email, stripeSubscriptionStatus } = session?.user; const { id, name, email, stripeSubscriptionStatus } = session?.user;
posthog.identify(id, { posthog?.identify(id, {
name, name,
email, email,
stripeSubscriptionStatus, stripeSubscriptionStatus,
}); });
} else if (status === "unauthenticated") { } else if (status === "unauthenticated") {
posthog.reset(); posthog?.reset();
} }
}, [posthog, session, status]); }, [posthog, session, status]);

View file

@ -24,6 +24,10 @@ export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse
) { ) {
if (!webhookSecret || !stripe) {
return res.status(500).end("Stripe env variables not set");
}
if (req.method === "POST") { if (req.method === "POST") {
const buf = await buffer(req); const buf = await buffer(req);
const sig = req.headers["stripe-signature"]; const sig = req.headers["stripe-signature"];
@ -72,11 +76,11 @@ export default async function handler(
const userId = subscription.metadata.userId; const userId = subscription.metadata.userId;
if (userId) { if (userId) {
posthog.capture({ posthog?.capture({
distinctId: userId, distinctId: userId,
event: "stripe invoice.payment_failed", event: "stripe invoice.payment_failed",
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
} }
break; break;
case "customer.subscription.deleted": case "customer.subscription.deleted":

View file

@ -8,6 +8,14 @@ export const stripeRouter = createTRPCRouter({
.input(z.object({ billedAnnually: z.boolean() })) .input(z.object({ billedAnnually: z.boolean() }))
.mutation( .mutation(
async ({ ctx: { prisma, stripe, session, req, posthog }, input }) => { async ({ ctx: { prisma, stripe, session, req, posthog }, input }) => {
if (
!stripe ||
!env.STRIPE_ANNUAL_PRICE_ID ||
!env.STRIPE_MONTHLY_PRICE_ID
) {
throw new Error("Stripe env variables not set");
}
const customerId = await getOrCreateStripeCustomerIdForUser({ const customerId = await getOrCreateStripeCustomerIdForUser({
prisma, prisma,
stripe, stripe,
@ -50,20 +58,24 @@ export const stripeRouter = createTRPCRouter({
throw new Error("Could not create checkout session"); throw new Error("Could not create checkout session");
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "visiting checkout page", event: "visiting checkout page",
properties: { properties: {
billingCycle: input.billedAnnually ? "annual" : "monthly", billingCycle: input.billedAnnually ? "annual" : "monthly",
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
return { checkoutUrl: checkoutSession.url }; return { checkoutUrl: checkoutSession.url };
} }
), ),
createBillingPortalSession: protectedProcedure.mutation( createBillingPortalSession: protectedProcedure.mutation(
async ({ ctx: { stripe, session, prisma, req, posthog } }) => { async ({ ctx: { stripe, session, prisma, req, posthog } }) => {
if (!stripe) {
throw new Error("Stripe env variables not set");
}
const customerId = await getOrCreateStripeCustomerIdForUser({ const customerId = await getOrCreateStripeCustomerIdForUser({
prisma, prisma,
stripe, stripe,
@ -89,14 +101,14 @@ export const stripeRouter = createTRPCRouter({
throw new Error("Could not create billing portal session"); throw new Error("Could not create billing portal session");
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "visiting billing portal page", event: "visiting billing portal page",
properties: { properties: {
stripeSubscriptionStatus: session.user.stripeSubscriptionStatus, stripeSubscriptionStatus: session.user.stripeSubscriptionStatus,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
return { billingPortalUrl: stripeBillingPortalSession.url }; return { billingPortalUrl: stripeBillingPortalSession.url };
} }

View file

@ -23,14 +23,14 @@ export const videoRouter = createTRPCRouter({
}, },
}); });
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "viewing video list", event: "viewing video list",
properties: { properties: {
videoAmount: videos.length, videoAmount: videos.length,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
const videosWithThumbnailUrl = await Promise.all( const videosWithThumbnailUrl = await Promise.all(
videos.map(async (video) => { videos.map(async (video) => {
@ -70,7 +70,7 @@ export const videoRouter = createTRPCRouter({
} }
if (session) { if (session) {
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "viewing video", event: "viewing video",
properties: { properties: {
@ -83,7 +83,7 @@ export const videoRouter = createTRPCRouter({
videoShareLinkExpiresAt: video.shareLinkExpiresAt, videoShareLinkExpiresAt: video.shareLinkExpiresAt,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
} }
const getObjectCommand = new GetObjectCommand({ const getObjectCommand = new GetObjectCommand({
@ -110,7 +110,7 @@ export const videoRouter = createTRPCRouter({
videos.length >= 10 && videos.length >= 10 &&
session.user.stripeSubscriptionStatus !== "active" session.user.stripeSubscriptionStatus !== "active"
) { ) {
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "hit video upload limit", event: "hit video upload limit",
properties: { properties: {
@ -118,7 +118,7 @@ export const videoRouter = createTRPCRouter({
stripeSubscriptionStatus: session.user.stripeSubscriptionStatus, stripeSubscriptionStatus: session.user.stripeSubscriptionStatus,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
throw new TRPCError({ throw new TRPCError({
code: "FORBIDDEN", code: "FORBIDDEN",
@ -127,7 +127,7 @@ export const videoRouter = createTRPCRouter({
}); });
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "uploading video", event: "uploading video",
properties: { properties: {
@ -135,7 +135,7 @@ export const videoRouter = createTRPCRouter({
stripeSubscriptionStatus: session.user.stripeSubscriptionStatus, stripeSubscriptionStatus: session.user.stripeSubscriptionStatus,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
const video = await prisma.video.create({ const video = await prisma.video.create({
data: { data: {
@ -184,7 +184,7 @@ export const videoRouter = createTRPCRouter({
throw new TRPCError({ code: "FORBIDDEN" }); throw new TRPCError({ code: "FORBIDDEN" });
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "update video setSharing", event: "update video setSharing",
properties: { properties: {
@ -192,7 +192,7 @@ export const videoRouter = createTRPCRouter({
videoSharing: input.sharing, videoSharing: input.sharing,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
return { return {
success: true, success: true,
@ -218,7 +218,7 @@ export const videoRouter = createTRPCRouter({
throw new TRPCError({ code: "FORBIDDEN" }); throw new TRPCError({ code: "FORBIDDEN" });
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "update video delete_after_link_expires", event: "update video delete_after_link_expires",
properties: { properties: {
@ -226,7 +226,7 @@ export const videoRouter = createTRPCRouter({
delete_after_link_expires: input.delete_after_link_expires, delete_after_link_expires: input.delete_after_link_expires,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
return { return {
success: true, success: true,
@ -255,7 +255,7 @@ export const videoRouter = createTRPCRouter({
throw new TRPCError({ code: "FORBIDDEN" }); throw new TRPCError({ code: "FORBIDDEN" });
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "update video shareLinkExpiresAt", event: "update video shareLinkExpiresAt",
properties: { properties: {
@ -263,7 +263,7 @@ export const videoRouter = createTRPCRouter({
shareLinkExpiresAt: input.shareLinkExpiresAt, shareLinkExpiresAt: input.shareLinkExpiresAt,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
return { return {
success: true, success: true,
@ -292,7 +292,7 @@ export const videoRouter = createTRPCRouter({
throw new TRPCError({ code: "FORBIDDEN" }); throw new TRPCError({ code: "FORBIDDEN" });
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "update video title", event: "update video title",
properties: { properties: {
@ -300,7 +300,7 @@ export const videoRouter = createTRPCRouter({
title: input.title, title: input.title,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
return { return {
success: true, success: true,
@ -325,14 +325,14 @@ export const videoRouter = createTRPCRouter({
throw new TRPCError({ code: "FORBIDDEN" }); throw new TRPCError({ code: "FORBIDDEN" });
} }
posthog.capture({ posthog?.capture({
distinctId: session.user.id, distinctId: session.user.id,
event: "video delete", event: "video delete",
properties: { properties: {
videoId: input.videoId, videoId: input.videoId,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
const deleteVideoObject = await s3.send( const deleteVideoObject = await s3.send(
new DeleteObjectCommand({ new DeleteObjectCommand({

View file

@ -125,10 +125,12 @@ const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) { if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" }); throw new TRPCError({ code: "UNAUTHORIZED" });
} }
if (rateLimit) {
const { success } = await rateLimit.limit(ctx.session.user.id); const { success } = await rateLimit.limit(ctx.session.user.id);
if (!success) { if (!success) {
throw new TRPCError({ code: "TOO_MANY_REQUESTS" }); throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
} }
}
return next({ return next({
ctx: { ctx: {

View file

@ -70,6 +70,7 @@ export const authOptions: NextAuthOptions = {
], ],
events: { events: {
async signIn(message) { async signIn(message) {
if (!!env.NEXT_PUBLIC_POSTHOG_KEY) {
const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_HOST, host: env.NEXT_PUBLIC_POSTHOG_HOST,
}); });
@ -84,8 +85,10 @@ export const authOptions: NextAuthOptions = {
}); });
await client.shutdownAsync(); await client.shutdownAsync();
}
}, },
async signOut(message) { async signOut(message) {
if (!!env.NEXT_PUBLIC_POSTHOG_KEY) {
const session = message.session as unknown as { const session = message.session as unknown as {
id: string; id: string;
sessionToken: string; sessionToken: string;
@ -104,6 +107,7 @@ export const authOptions: NextAuthOptions = {
}); });
await client.shutdownAsync(); await client.shutdownAsync();
}
}, },
}, },
pages: { pages: {

View file

@ -1,6 +1,8 @@
import { PostHog } from "posthog-node"; import { PostHog } from "posthog-node";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
export const posthog = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { export const posthog = !!env.NEXT_PUBLIC_POSTHOG_KEY
? new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_HOST, host: env.NEXT_PUBLIC_POSTHOG_HOST,
}); })
: null;

View file

@ -2,10 +2,13 @@ import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis"; import { Redis } from "@upstash/redis";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
export const rateLimit = new Ratelimit({ export const rateLimit =
!!env.UPSTASH_REDIS_REST_URL && !!env.UPSTASH_REDIS_REST_TOKEN
? new Ratelimit({
redis: new Redis({ redis: new Redis({
url: env.UPSTASH_REDIS_REST_URL, url: env.UPSTASH_REDIS_REST_URL,
token: env.UPSTASH_REDIS_REST_TOKEN, token: env.UPSTASH_REDIS_REST_TOKEN,
}), }),
limiter: Ratelimit.slidingWindow(60, "60 s"), limiter: Ratelimit.slidingWindow(60, "60 s"),
}); })
: null;

View file

@ -60,7 +60,7 @@ export const handleInvoicePaid = async ({
event: Stripe.Event; event: Stripe.Event;
stripe: Stripe; stripe: Stripe;
prisma: PrismaClient; prisma: PrismaClient;
posthog: PostHog; posthog: PostHog | null;
}) => { }) => {
const invoice = event.data.object as Stripe.Invoice; const invoice = event.data.object as Stripe.Invoice;
const subscriptionId = invoice.subscription; const subscriptionId = invoice.subscription;
@ -81,14 +81,14 @@ export const handleInvoicePaid = async ({
}); });
if (userId && subscription.status) { if (userId && subscription.status) {
posthog.capture({ posthog?.capture({
distinctId: userId, distinctId: userId,
event: "stripe invoice.paid", event: "stripe invoice.paid",
properties: { properties: {
stripeSubscriptionStatus: subscription.status, stripeSubscriptionStatus: subscription.status,
}, },
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
} }
}; };
@ -99,7 +99,7 @@ export const handleSubscriptionCreatedOrUpdated = async ({
}: { }: {
event: Stripe.Event; event: Stripe.Event;
prisma: PrismaClient; prisma: PrismaClient;
posthog: PostHog; posthog: PostHog | null;
}) => { }) => {
const subscription = event.data.object as Stripe.Subscription; const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata.userId; const userId = subscription.metadata.userId;
@ -116,11 +116,11 @@ export const handleSubscriptionCreatedOrUpdated = async ({
}); });
if (userId && subscription.status) { if (userId && subscription.status) {
posthog.capture({ posthog?.capture({
distinctId: userId, distinctId: userId,
event: "stripe subscription created or updated", event: "stripe subscription created or updated",
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
} }
}; };
@ -131,7 +131,7 @@ export const handleSubscriptionCanceled = async ({
}: { }: {
event: Stripe.Event; event: Stripe.Event;
prisma: PrismaClient; prisma: PrismaClient;
posthog: PostHog; posthog: PostHog | null;
}) => { }) => {
const subscription = event.data.object as Stripe.Subscription; const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata.userId; const userId = subscription.metadata.userId;
@ -148,10 +148,10 @@ export const handleSubscriptionCanceled = async ({
}); });
if (userId && subscription.status) { if (userId && subscription.status) {
posthog.capture({ posthog?.capture({
distinctId: userId, distinctId: userId,
event: "stripe subscription cancelled", event: "stripe subscription cancelled",
}); });
void posthog.shutdownAsync(); void posthog?.shutdownAsync();
} }
}; };

View file

@ -1,5 +1,8 @@
import Stripe from "stripe"; import Stripe from "stripe";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
export const stripe = !!env.STRIPE_SECRET_KEY
? new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: "2022-11-15", apiVersion: "2022-11-15",
}); })
: null;