feat: add next-runtime-env

This commit is contained in:
Catalin Pit
2024-01-25 10:48:20 +02:00
parent bc1d5cea0a
commit d451a7acce
34 changed files with 192 additions and 56 deletions

View File

@ -1,8 +1,12 @@
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
import { env } from 'next-runtime-env';
const NEXT_PUBLIC_PROJECT = process.env.NEXT_PUBLIC_PROJECT;
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL');
export const IS_APP_MARKETING = NEXT_PUBLIC_PROJECT === 'marketing';
export const IS_APP_WEB = NEXT_PUBLIC_PROJECT === 'web';
export const APP_FOLDER = IS_APP_MARKETING ? 'marketing' : 'web';
export const APP_BASE_URL = IS_APP_WEB
? process.env.NEXT_PUBLIC_WEBAPP_URL
: process.env.NEXT_PUBLIC_MARKETING_URL;
export const APP_BASE_URL = IS_APP_WEB ? NEXT_PUBLIC_WEBAPP_URL : NEXT_PUBLIC_MARKETING_URL;

View File

@ -1,5 +1,10 @@
import { env } from 'next-runtime-env';
import { APP_BASE_URL } from './app';
const NEXT_PUBLIC_FEATURE_BILLING_ENABLED = env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED');
const NEXT_PUBLIC_POSTHOG_KEY = env('NEXT_PUBLIC_POSTHOG_KEY');
/**
* The flag name for global session recording feature flag.
*/
@ -16,7 +21,7 @@ export const FEATURE_FLAG_POLL_INTERVAL = 30000;
* Does not take any person or group properties into account.
*/
export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
app_billing: process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true',
app_billing: NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true',
marketing_header_single_player_mode: false,
} as const;
@ -24,7 +29,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
* Extract the PostHog configuration from the environment.
*/
export function extractPostHogConfig(): { key: string; host: string } | null {
const postHogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
const postHogKey = NEXT_PUBLIC_POSTHOG_KEY;
const postHogHost = `${APP_BASE_URL}/ingest`;
if (!postHogKey || !postHogHost) {

View File

@ -7,6 +7,7 @@ import type { JWT } from 'next-auth/jwt';
import CredentialsProvider from 'next-auth/providers/credentials';
import type { GoogleProfile } from 'next-auth/providers/google';
import GoogleProvider from 'next-auth/providers/google';
import { env } from 'next-runtime-env';
import { prisma } from '@documenso/prisma';
@ -15,6 +16,8 @@ import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa
import { getUserByEmail } from '../server-only/user/get-user-by-email';
import { ErrorCode } from './error-codes';
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
export const NEXT_AUTH_OPTIONS: AuthOptions = {
adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET ?? 'secret',
@ -166,7 +169,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
async signIn({ user }) {
// We do this to stop OAuth providers from creating an account
// when signups are disabled
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === 'true') {
if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') {
const userData = await getUserByEmail({ email: user.email! });
return !!userData;

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email';
@ -10,6 +12,10 @@ export interface SendConfirmationEmailProps {
}
export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailProps) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const NEXT_PRIVATE_SMTP_FROM_NAME = env('NEXT_PRIVATE_SMTP_FROM_NAME');
const NEXT_PRIVATE_SMTP_FROM_ADDRESS = env('NEXT_PRIVATE_SMTP_FROM_ADDRESS');
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@ -30,10 +36,10 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro
throw new Error('Verification token not found for the user');
}
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const confirmationLink = `${assetBaseUrl}/verify-email/${verificationToken.token}`;
const senderName = process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso';
const senderAdress = process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com';
const senderName = NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso';
const senderAdress = NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com';
const confirmationTemplate = createElement(ConfirmEmailTemplate, {
assetBaseUrl,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
@ -10,6 +12,8 @@ export interface SendForgotPasswordOptions {
}
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@ -29,8 +33,8 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
}
const token = user.PasswordResetToken[0].token;
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const resetPasswordLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`;
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const resetPasswordLink = `${NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`;
const template = createElement(ForgotPasswordTemplate, {
assetBaseUrl,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
@ -10,13 +12,15 @@ export interface SendResetPasswordOptions {
}
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
});
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const template = createElement(ResetPasswordTemplate, {
assetBaseUrl,

View File

@ -2,6 +2,8 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
@ -16,6 +18,8 @@ export type DeleteDocumentOptions = {
status: DocumentStatus;
};
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptions) => {
// if the document is a draft, hard-delete
if (status === DocumentStatus.DRAFT) {
@ -49,7 +53,7 @@ export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptio
if (document.Recipient.length > 0) {
await Promise.all(
document.Recipient.map(async (recipient) => {
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const template = createElement(DocumentCancelTemplate, {
documentName: document.title,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
@ -14,6 +16,8 @@ export type ResendDocumentOptions = {
recipients: number[];
};
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const resendDocument = async ({ documentId, userId, recipients }: ResendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
@ -67,8 +71,8 @@ export const resendDocument = async ({ documentId, userId, recipients }: ResendD
'document.name': document.title,
};
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
const template = createElement(DocumentInviteEmailTemplate, {
documentName: document.title,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
@ -12,6 +14,8 @@ export interface SendDocumentOptions {
}
export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const document = await prisma.document.findUnique({
where: {
id: documentId,
@ -36,12 +40,12 @@ export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) =>
document.Recipient.map(async (recipient) => {
const { email, name, token } = recipient;
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const template = createElement(DocumentCompletedEmailTemplate, {
documentName: document.title,
assetBaseUrl,
downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
downloadLink: `${NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
});
await mailer.sendMail({

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
@ -14,6 +16,8 @@ export type SendDocumentOptions = {
};
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@ -59,8 +63,8 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
return;
}
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
const template = createElement(DocumentInviteEmailTemplate, {
documentName: document.title,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
@ -11,6 +13,8 @@ export interface SendPendingEmailOptions {
}
export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const document = await prisma.document.findFirst({
where: {
id: documentId,
@ -41,7 +45,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
const { email, name } = recipient;
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const template = createElement(DocumentPendingEmailTemplate, {
documentName: document.title,

View File

@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
import { env } from 'next-runtime-env';
import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags';
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
@ -11,6 +12,9 @@ import { extractDistinctUserId, mapJwtToFlagProperties } from './get';
* Get all the evaluated feature flags based on the current user if possible.
*/
export default async function handlerFeatureFlagAll(req: Request) {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL');
const requestHeaders = Object.fromEntries(req.headers.entries());
const nextReq = new NextRequest(req, {
@ -38,11 +42,11 @@ export default async function handlerFeatureFlagAll(req: Request) {
const origin = req.headers.get('origin');
if (origin) {
if (origin.startsWith(process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
if (origin.startsWith(NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
res.headers.set('Access-Control-Allow-Origin', origin);
}
if (origin.startsWith(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
res.headers.set('Access-Control-Allow-Origin', origin);
}
}

View File

@ -1,7 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import { nanoid } from 'nanoid';
import { JWT, getToken } from 'next-auth/jwt';
import type { JWT } from 'next-auth/jwt';
import { getToken } from 'next-auth/jwt';
import { env } from 'next-runtime-env';
import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
@ -13,6 +15,9 @@ import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-po
* @returns A Response with the feature flag value.
*/
export default async function handleFeatureFlagGet(req: Request) {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL');
const { searchParams } = new URL(req.url ?? '');
const flag = searchParams.get('flag');
@ -57,11 +62,11 @@ export default async function handleFeatureFlagGet(req: Request) {
const origin = req.headers.get('Origin');
if (origin) {
if (origin.startsWith(process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
if (origin.startsWith(NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) {
res.headers.set('Access-Control-Allow-Origin', origin);
}
if (origin.startsWith(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) {
res.headers.set('Access-Control-Allow-Origin', origin);
}
}

View File

@ -1,4 +1,8 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { env } from 'next-runtime-env';
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const getBaseUrl = () => {
if (typeof window !== 'undefined') {
return '';
@ -8,8 +12,8 @@ export const getBaseUrl = () => {
return `https://${process.env.VERCEL_URL}`;
}
if (process.env.NEXT_PUBLIC_WEBAPP_URL) {
return process.env.NEXT_PUBLIC_WEBAPP_URL;
if (NEXT_PUBLIC_WEBAPP_URL) {
return NEXT_PUBLIC_WEBAPP_URL;
}
return `http://localhost:${process.env.PORT ?? 3000}`;

View File

@ -1,4 +1,5 @@
import { base64 } from '@scure/base';
import { env } from 'next-runtime-env';
import { match } from 'ts-pattern';
import { DocumentDataType } from '@documenso/prisma/client';
@ -11,8 +12,10 @@ type File = {
arrayBuffer: () => Promise<ArrayBuffer>;
};
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
export const putFile = async (file: File) => {
const { type, data } = await match(process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT)
const { type, data } = await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
.with('s3', async () => putFileInS3(file))
.otherwise(async () => putFileInDatabase(file));

View File

@ -11,12 +11,15 @@ import {
} from '@aws-sdk/client-s3';
import slugify from '@sindresorhus/slugify';
import { type JWT, getToken } from 'next-auth/jwt';
import { env } from 'next-runtime-env';
import path from 'node:path';
import { APP_BASE_URL } from '../../constants/app';
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
import { alphaid } from '../id';
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
export const getPresignPostUrl = async (fileName: string, contentType: string) => {
const client = getS3Client();
@ -117,7 +120,7 @@ export const deleteS3File = async (key: string) => {
};
const getS3Client = () => {
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
if (NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
throw new Error('Invalid upload transport');
}