Add t3 env for env validation (#498)

* add t3 env for env validation

* fix env variables that should be optional

* update gitignore

* add vercel ignore
This commit is contained in:
Matti Nannt
2023-07-07 15:58:15 +02:00
committed by GitHub
parent ffb9fd659f
commit 4017a5c4f9
27 changed files with 232 additions and 101 deletions

View File

@@ -46,6 +46,7 @@ NEXTAUTH_URL=http://localhost:3000
# MAIL_FROM=noreply@example.com
# SMTP_HOST=localhost
# SMTP_PORT=1025
# Enable SMTP_SECURE_ENABLED for TLS (port 465)
# SMTP_SECURE_ENABLED=0 # Enable for TLS (port 465)
# SMTP_USER=smtpUser
# SMTP_PASSWORD=smtpPassword

View File

@@ -82,12 +82,6 @@ NEXT_PUBLIC_PRIVACY_URL=
NEXT_PUBLIC_TERMS_URL=
NEXT_PUBLIC_IMPRINT_URL=
# Disable Sentry warning
SENTRY_IGNORE_API_RESOLUTION_ERROR=1
# Enable Sentry Error Tracking
NEXT_PUBLIC_SENTRY_DSN=
# Configure Github Login
NEXT_PUBLIC_GITHUB_AUTH_ENABLED=0
GITHUB_ID=
@@ -100,7 +94,6 @@ GOOGLE_CLIENT_SECRET=
# Stripe Billing Variables
NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ yarn-error.log*
.env.test.local
.env.production.local
!packages/database/.env
!apps/web/.env
# Prisma generated files
packages/database/zod

1
.vercelignore Normal file
View File

@@ -0,0 +1 @@
apps/web/.env

1
apps/web/.env Symbolic link
View File

@@ -0,0 +1 @@
../../.env

View File

@@ -1,13 +1,14 @@
"use client";
import { env } from "@/env.mjs";
import { formbricksEnabled } from "@/lib/formbricks";
import formbricks from "@formbricks/js";
import { useEffect } from "react";
/* if (typeof window !== "undefined" && formbricksEnabled) {
formbricks.init({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
environmentId: env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
apiHost: env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
logLevel: "debug",
});
} */
@@ -16,8 +17,8 @@ export default function FormbricksClient({ session }) {
useEffect(() => {
if (formbricksEnabled && session.user && formbricks) {
formbricks.init({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
environmentId: env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
apiHost: env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
});
formbricks.setUserId(session.user.id);
formbricks.setEmail(session.user.email);

View File

@@ -1,21 +1,22 @@
"use client";
import { env } from "@/env.mjs";
import { usePathname, useSearchParams } from "next/navigation";
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { useEffect } from "react";
const posthogEnabled = process.env.NEXT_PUBLIC_POSTHOG_API_KEY && process.env.NEXT_PUBLIC_POSTHOG_API_HOST;
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;
// Check that PostHog is client-side (used to handle Next.js SSR)
if (
typeof window !== "undefined" &&
posthogEnabled &&
typeof process.env.NEXT_PUBLIC_POSTHOG_API_KEY === "string" &&
typeof process.env.NEXT_PUBLIC_POSTHOG_API_HOST === "string"
typeof env.NEXT_PUBLIC_POSTHOG_API_KEY === "string" &&
typeof env.NEXT_PUBLIC_POSTHOG_API_HOST === "string"
) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
posthog.init(env.NEXT_PUBLIC_POSTHOG_API_KEY, {
api_host: env.NEXT_PUBLIC_POSTHOG_API_HOST,
// Disable in development
loaded: (posthog) => {
if (process.env.NODE_ENV === "development") posthog.opt_out_capturing();

View File

@@ -1,3 +1,4 @@
import { env } from "@/env.mjs";
import { verifyPassword } from "@/lib/auth";
import { verifyToken } from "@/lib/jwt";
import { prisma } from "@formbricks/database";
@@ -120,12 +121,12 @@ export const authOptions: NextAuthOptions = {
},
}),
GitHubProvider({
clientId: process.env.GITHUB_ID || "",
clientSecret: process.env.GITHUB_SECRET || "",
clientId: env.GITHUB_ID || "",
clientSecret: env.GITHUB_SECRET || "",
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID || "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
clientId: env.GOOGLE_CLIENT_ID || "",
clientSecret: env.GOOGLE_CLIENT_SECRET || "",
allowDangerousEmailAccountLinking: true,
}),
],
@@ -189,7 +190,7 @@ export const authOptions: NextAuthOptions = {
},
async signIn({ user, account }: any) {
if (account.provider === "credentials" || account.provider === "token") {
if (!user.emailVerified && process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED !== "1") {
if (!user.emailVerified && env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED !== "1") {
return `/auth/verification-requested?email=${encodeURIComponent(user.email)}`;
}
return true;

View File

@@ -3,14 +3,11 @@ import { verifyInviteToken } from "@/lib/jwt";
import { populateEnvironment } from "@/lib/populate";
import { prisma } from "@formbricks/database";
import { NextResponse } from "next/server";
import { env } from "@/env.mjs";
export async function POST(request: Request) {
let { inviteToken, ...user } = await request.json();
if (
inviteToken
? process.env.NEXT_PUBLIC_INVITE_DISABLED === "1"
: process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1"
) {
if (inviteToken ? env.NEXT_PUBLIC_INVITE_DISABLED === "1" : env.NEXT_PUBLIC_SIGNUP_DISABLED === "1") {
return NextResponse.json({ error: "Signup disabled" }, { status: 403 });
}
user = { ...user, ...{ email: user.email.toLowerCase() } };
@@ -99,7 +96,7 @@ export async function POST(request: Request) {
await prisma.invite.delete({ where: { id: inviteId } });
}
if (process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED !== "1") {
if (env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED !== "1") {
await sendVerificationEmail(userData);
}
return NextResponse.json(userData);

View File

@@ -5,6 +5,7 @@ import { useSearchParams } from "next/navigation";
import { SignupForm } from "@/components/auth/SignupForm";
import FormWrapper from "@/components/auth/FormWrapper";
import Testimonial from "@/components/auth/Testimonial";
import { env } from "@/env.mjs";
export default function SignUpPage() {
const searchParams = useSearchParams();
@@ -18,9 +19,7 @@ export default function SignUpPage() {
<div className="col-span-3 flex flex-col items-center justify-center">
<FormWrapper>
{(
inviteToken
? process.env.NEXT_PUBLIC_INVITE_DISABLED === "1"
: process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1"
inviteToken ? env.NEXT_PUBLIC_INVITE_DISABLED === "1" : env.NEXT_PUBLIC_SIGNUP_DISABLED === "1"
) ? (
<>
<h1 className="leading-2 mb-4 text-center font-bold">Sign up disabled</h1>

View File

@@ -1,9 +1,10 @@
"use client";
import { env } from "@/env.mjs";
import type { Session } from "next-auth";
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";
const posthogEnabled = process.env.NEXT_PUBLIC_POSTHOG_API_KEY && process.env.NEXT_PUBLIC_POSTHOG_API_HOST;
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;
export default function PosthogIdentify({ session }: { session: Session }) {
const posthog = usePostHog();

View File

@@ -1,7 +1,10 @@
"use client";
import ShareInviteModal from "@/app/environments/[environmentId]/settings/members/ShareInviteModal";
import DeleteDialog from "@/components/shared/DeleteDialog";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import CreateTeamModal from "@/components/team/CreateTeamModal";
import { env } from "@/env.mjs";
import {
addMember,
deleteInvite,
@@ -12,6 +15,8 @@ import {
updateMemberRole,
useMembers,
} from "@/lib/members";
import { useProfile } from "@/lib/profile";
import { capitalizeFirstLetter } from "@/lib/utils";
import {
Badge,
Button,
@@ -28,15 +33,11 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@formbricks/ui";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { PaperAirplaneIcon, ShareIcon, TrashIcon } from "@heroicons/react/24/outline";
import { useState } from "react";
import toast from "react-hot-toast";
import AddMemberModal from "./AddMemberModal";
import CreateTeamModal from "@/components/team/CreateTeamModal";
import { capitalizeFirstLetter } from "@/lib/utils";
import { useProfile } from "@/lib/profile";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import ShareInviteModal from "@/app/environments/[environmentId]/settings/members/ShareInviteModal";
type EditMembershipsProps = {
environmentId: string;
@@ -191,7 +192,7 @@ export function EditMemberships({ environmentId }: EditMembershipsProps) {
}}>
Create New Team
</Button>
{process.env.NEXT_PUBLIC_INVITE_DISABLED !== "1" && isAdminOrOwner && (
{env.NEXT_PUBLIC_INVITE_DISABLED !== "1" && isAdminOrOwner && (
<Button
variant="darkCTA"
onClick={() => {

View File

@@ -2,6 +2,7 @@
import Headline from "@/components/preview/Headline";
import Subheader from "@/components/preview/Subheader";
import { env } from "@/env.mjs";
import { formbricksEnabled, updateResponse } from "@/lib/formbricks";
import { useProfile } from "@/lib/profile";
import { useProfileMutation } from "@/lib/profile/mutateProfile";
@@ -49,11 +50,7 @@ const Objective: React.FC<ObjectiveProps> = ({ next, skip, formbricksResponseId
console.error(e);
toast.error("An error occured saving your settings");
}
if (
formbricksEnabled &&
process.env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID &&
formbricksResponseId
) {
if (formbricksEnabled && env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID && formbricksResponseId) {
const res = await updateResponse(
formbricksResponseId,
{

View File

@@ -3,14 +3,14 @@
import { cn } from "@/../../packages/lib/cn";
import Headline from "@/components/preview/Headline";
import Subheader from "@/components/preview/Subheader";
import { env } from "@/env.mjs";
import { createResponse, formbricksEnabled } from "@/lib/formbricks";
import { useProfile } from "@/lib/profile";
import { useProfileMutation } from "@/lib/profile/mutateProfile";
import { SurveyId } from "@formbricks/js";
import { ResponseId, SurveyId } from "@formbricks/js";
import { Button } from "@formbricks/ui";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { ResponseId } from "@formbricks/js";
type RoleProps = {
next: () => void;
@@ -48,13 +48,10 @@ const Role: React.FC<RoleProps> = ({ next, skip, setFormbricksResponseId }) => {
toast.error("An error occured saving your settings");
console.error(e);
}
if (formbricksEnabled && process.env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID) {
const res = await createResponse(
process.env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID as SurveyId,
{
role: selectedRole.label,
}
);
if (formbricksEnabled && env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID) {
const res = await createResponse(env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID as SurveyId, {
role: selectedRole.label,
});
if (res.ok) {
const response = res.data;
setFormbricksResponseId(response.id);

View File

@@ -1,6 +1,7 @@
"use client";
import { GoogleButton } from "@/components/auth/GoogleButton";
import { env } from "@/env.mjs";
import { Button, PasswordInput } from "@formbricks/ui";
import { XCircleIcon } from "@heroicons/react/24/solid";
import { signIn } from "next-auth/react";
@@ -75,7 +76,7 @@ export const SigninForm = () => {
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
{process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && isPasswordFocused && (
{env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && isPasswordFocused && (
<div className="ml-1 text-right transition-all duration-500 ease-in-out">
<Link
href="/auth/forgot-password"
@@ -105,18 +106,18 @@ export const SigninForm = () => {
</Button>
</form>
{process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED === "1" && (
{env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED === "1" && (
<>
<GoogleButton />
</>
)}
{process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
{env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
<>
<GithubButton />
</>
)}
</div>
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
{env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
<div className="mt-9 text-center text-xs ">
<span className="leading-5 text-slate-500">New to Formbricks?</span>
<br />

View File

@@ -1,15 +1,15 @@
"use client";
import { Button } from "@formbricks/ui";
import { PasswordInput } from "@formbricks/ui";
import { GoogleButton } from "@/components/auth/GoogleButton";
import IsPasswordValid from "@/components/auth/IsPasswordValid";
import { env } from "@/env.mjs";
import { createUser } from "@/lib/users/users";
import { Button, PasswordInput } from "@formbricks/ui";
import { XCircleIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { useRef, useState } from "react";
import { GithubButton } from "./GithubButton";
import { GoogleButton } from "@/components/auth/GoogleButton";
import IsPasswordValid from "@/components/auth/IsPasswordValid";
export const SignupForm = () => {
const searchParams = useSearchParams();
@@ -33,7 +33,7 @@ export const SignupForm = () => {
searchParams?.get("inviteToken")
);
const url =
process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED === "1"
env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED === "1"
? `/auth/signup-without-verification-success`
: `/auth/verification-requested?email=${encodeURIComponent(e.target.elements.email.value)}`;
@@ -131,7 +131,7 @@ export const SignupForm = () => {
className="focus:border-brand focus:ring-brand block w-full rounded-md shadow-sm sm:text-sm"
/>
</div>
{process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && isPasswordFocused && (
{env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && isPasswordFocused && (
<div className="ml-1 text-right transition-all duration-500 ease-in-out">
<Link
href="/auth/forgot-password"
@@ -163,36 +163,36 @@ export const SignupForm = () => {
</Button>
</form>
{process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED === "1" && (
{env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED === "1" && (
<>
<GoogleButton />
</>
)}
{process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
{env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
<>
<GithubButton />{" "}
</>
)}
</div>
{(process.env.NEXT_PUBLIC_TERMS_URL || process.env.NEXT_PUBLIC_PRIVACY_URL) && (
{(env.NEXT_PUBLIC_TERMS_URL || env.NEXT_PUBLIC_PRIVACY_URL) && (
<div className="mt-3 text-center text-xs text-slate-500">
By signing up, you agree to our
<br />
{process.env.NEXT_PUBLIC_TERMS_URL && (
{env.NEXT_PUBLIC_TERMS_URL && (
<Link
className="font-semibold"
href="google.com" /* {process.env.NEXT_PUBLIC_TERMS_URL} */
href="google.com" /* {env.NEXT_PUBLIC_TERMS_URL} */
rel="noreferrer"
target="_blank">
Terms of Service
</Link>
)}
{process.env.NEXT_PUBLIC_TERMS_URL && process.env.NEXT_PUBLIC_PRIVACY_URL && <span> and </span>}
{process.env.NEXT_PUBLIC_PRIVACY_URL && (
{env.NEXT_PUBLIC_TERMS_URL && env.NEXT_PUBLIC_PRIVACY_URL && <span> and </span>}
{env.NEXT_PUBLIC_PRIVACY_URL && (
<Link
className="font-semibold"
href="google.com" /* {/* process.env.NEXT_PUBLIC_PRIVACY_URL }*/
href="google.com" /* {/* env.NEXT_PUBLIC_PRIVACY_URL }*/
rel="noreferrer"
target="_blank">
Privacy Policy.

View File

@@ -1,18 +1,19 @@
import { env } from "@/env.mjs";
import Link from "next/link";
export default function LegalFooter() {
if (!process.env.NEXT_PUBLIC_IMPRINT_URL && !process.env.NEXT_PUBLIC_PRIVACY_URL) return null;
if (!env.NEXT_PUBLIC_IMPRINT_URL && !env.NEXT_PUBLIC_PRIVACY_URL) return null;
return (
<div className="top-0 z-10 w-full border-b bg-white">
<div className="mx-auto max-w-lg p-3 text-center text-sm text-slate-400">
{process.env.NEXT_PUBLIC_IMPRINT_URL && (
<Link href={process.env.NEXT_PUBLIC_IMPRINT_URL} target="_blank">
{env.NEXT_PUBLIC_IMPRINT_URL && (
<Link href={env.NEXT_PUBLIC_IMPRINT_URL} target="_blank">
Imprint
</Link>
)}
{process.env.NEXT_PUBLIC_IMPRINT_URL && process.env.NEXT_PUBLIC_PRIVACY_URL && <span> | </span>}
{process.env.NEXT_PUBLIC_PRIVACY_URL && (
<Link href={process.env.NEXT_PUBLIC_PRIVACY_URL} target="_blank">
{env.NEXT_PUBLIC_IMPRINT_URL && env.NEXT_PUBLIC_PRIVACY_URL && <span> | </span>}
{env.NEXT_PUBLIC_PRIVACY_URL && (
<Link href={env.NEXT_PUBLIC_PRIVACY_URL} target="_blank">
Privacy Policy
</Link>
)}

110
apps/web/env.mjs Normal file
View File

@@ -0,0 +1,110 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
/*
* Serverside Environment variables, not available on the client.
* Will throw if you access these variables on the client.
*/
server: {
DATABASE_URL: z.string().url(),
PRISMA_GENERATE_DATAPROXY: z.enum(["1", "0", ""]).optional(),
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url().optional(),
MAIL_FROM: z.string().email().optional(),
SMTP_HOST: z.string().min(1).optional(),
SMTP_PORT: z.string().min(1).optional(),
SMTP_USER: z.string().min(1).optional(),
SMTP_PASSWORD: z.string().min(1).optional(),
SMTP_SECURE_ENABLED: z.enum(["1", "0"]).optional(),
GITHUB_ID: z.string().optional(),
GITHUB_SECRET: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
CRON_SECRET: z.string().optional(),
NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional(),
NEXT_PUBLIC_POSTHOG_API_HOST: z.string().optional(),
},
/*
* Environment variables available on the client (and server).
*
* 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_.
*/
client: {
NEXT_PUBLIC_WEBAPP_URL: z.string().url().optional(),
NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
NEXT_PUBLIC_PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
NEXT_PUBLIC_SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
NEXT_PUBLIC_INVITE_DISABLED: z.enum(["1", "0"]).optional(),
NEXT_PUBLIC_PRIVACY_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
NEXT_PUBLIC_TERMS_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
NEXT_PUBLIC_IMPRINT_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
NEXT_PUBLIC_GITHUB_AUTH_ENABLED: z.enum(["1", "0"]).optional(),
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED: z.enum(["1", "0"]).optional(),
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: z.string().optional(),
NEXT_PUBLIC_FORMBRICKS_API_HOST: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: z.string().optional(),
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: z.string().optional(),
NEXT_PUBLIC_IS_FORMBRICKS_CLOUD: z.enum(["1", "0"]).optional(),
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
* we need to manually destructure them to make sure all are included in bundle.
*
* 💡 You'll get type errors if not all variables from `server` & `client` are included here.
*/
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
PRISMA_GENERATE_DATAPROXY: process.env.PRISMA_GENERATE_DATAPROXY,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
MAIL_FROM: process.env.MAIL_FROM,
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_USER: process.env.SMTP_USER,
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
SMTP_SECURE_ENABLED: process.env.SMTP_SECURE_ENABLED,
GITHUB_ID: process.env.GITHUB_ID,
GITHUB_SECRET: process.env.GITHUB_SECRET,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
CRON_SECRET: process.env.CRON_SECRET,
NEXT_PUBLIC_WEBAPP_URL: process.env.NEXT_PUBLIC_WEBAPP_URL,
NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED: process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED,
NEXT_PUBLIC_PASSWORD_RESET_DISABLED: process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED,
NEXT_PUBLIC_SIGNUP_DISABLED: process.env.NEXT_PUBLIC_SIGNUP_DISABLED,
NEXT_PUBLIC_INVITE_DISABLED: process.env.NEXT_PUBLIC_INVITE_DISABLED,
NEXT_PUBLIC_PRIVACY_URL: process.env.NEXT_PUBLIC_PRIVACY_URL,
NEXT_PUBLIC_TERMS_URL: process.env.NEXT_PUBLIC_TERMS_URL,
NEXT_PUBLIC_IMPRINT_URL: process.env.NEXT_PUBLIC_IMPRINT_URL,
NEXT_PUBLIC_GITHUB_AUTH_ENABLED: process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED,
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED: process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED,
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY,
NEXT_PUBLIC_FORMBRICKS_API_HOST: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID,
NEXT_PUBLIC_IS_FORMBRICKS_CLOUD: process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD,
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
NEXT_PUBLIC_POSTHOG_API_HOST: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
},
});

View File

@@ -1,3 +1,4 @@
import { env } from "@/env.mjs";
import { getQuestionResponseMapping } from "@/lib/responses/questionResponseMapping";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { Question } from "@formbricks/types/questions";
@@ -18,18 +19,18 @@ interface sendEmailData {
export const sendEmail = async (emailData: sendEmailData) => {
let transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: process.env.SMTP_SECURE_ENABLED === "1", // true for 465, false for other ports
host: env.SMTP_HOST,
port: env.SMTP_PORT,
secure: env.SMTP_SECURE_ENABLED === "1", // true for 465, false for other ports
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
user: env.SMTP_USER,
pass: env.SMTP_PASSWORD,
},
// logger: true,
// debug: true,
});
const emailDefaults = {
from: `Formbricks <${process.env.MAIL_FROM || "noreply@formbricks.com"}>`,
from: `Formbricks <${env.MAIL_FROM || "noreply@formbricks.com"}>`,
};
await transporter.sendMail({ ...emailDefaults, ...emailData });
};
@@ -128,7 +129,7 @@ export const sendResponseFinishedEmail = async (
subject: personEmail
? `${personEmail} just completed your ${survey.name} survey ✅`
: `A response for ${survey.name} was completed ✅`,
replyTo: personEmail || process.env.MAIL_FROM,
replyTo: personEmail || env.MAIL_FROM,
html: withEmailTemplate(`<h1>Survey completed</h1>Someone just completed your survey "${survey.name}"<br/>
<hr/>

View File

@@ -1,7 +1,8 @@
import formbricks, { PersonId, SurveyId, ResponseId } from "@formbricks/js";
import { env } from "@/env.mjs";
export const formbricksEnabled =
typeof process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST && process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID;
typeof env.NEXT_PUBLIC_FORMBRICKS_API_HOST && env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID;
export const createResponse = async (
surveyId: SurveyId,

View File

@@ -1,8 +1,9 @@
import jwt from "jsonwebtoken";
import { prisma } from "@formbricks/database";
import { env } from "@/env.mjs";
export function createToken(userId, userEmail, options = {}) {
return jwt.sign({ id: userId }, process.env.NEXTAUTH_SECRET + userEmail, options);
return jwt.sign({ id: userId }, env.NEXTAUTH_SECRET + userEmail, options);
}
export async function verifyToken(token, userEmail = "") {
@@ -20,11 +21,11 @@ export async function verifyToken(token, userEmail = "") {
userEmail = foundUser.email;
}
return jwt.verify(token, process.env.NEXTAUTH_SECRET + userEmail);
return jwt.verify(token, env.NEXTAUTH_SECRET + userEmail);
}
export const createInviteToken = (inviteId: string, email: string, options = {}) => {
return jwt.sign({ inviteId, email }, process.env.NEXTAUTH_SECRET, options);
return jwt.sign({ inviteId, email }, env.NEXTAUTH_SECRET, options);
};
export const verifyInviteToken = async (token: string) => {

View File

@@ -1,9 +1,8 @@
import "./env.mjs";
import { createId } from "@paralleldrive/cuid2";
/** @type {import('next').NextConfig} */
require("@next/env").loadEnvConfig("../../");
const { createId } = require("@paralleldrive/cuid2");
const nextConfig = {
output: "standalone",
experimental: {
@@ -66,4 +65,4 @@ const nextConfig = {
},
};
module.exports = nextConfig;
export default nextConfig;

View File

@@ -21,10 +21,10 @@
"@headlessui/react": "^1.7.15",
"@heroicons/react": "^2.0.18",
"@json2csv/node": "^7.0.1",
"@next/env": "^13.4.8",
"@paralleldrive/cuid2": "^2.2.1",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@t3-oss/env-nextjs": "^0.6.0",
"bcryptjs": "^2.4.3",
"eslint-config-next": "^13.4.8",
"jsonwebtoken": "^9.0.0",

View File

@@ -1,3 +1,4 @@
import { env } from "@/env.mjs";
import { getPlan, hasEnvironmentAccess } from "@/lib/api/apiHelper";
import { prisma } from "@formbricks/database";
import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
@@ -71,7 +72,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
},
});
if (process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD === "1") {
if (env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD === "1") {
const plan = await getPlan(req, res);
if (plan === "free" && responses.length > RESPONSES_LIMIT_FREE) {
return res.json({

View File

@@ -1,3 +1,4 @@
import { env } from "@/env.mjs";
import { getSessionUser, hasTeamAccess, isAdminOrOwner } from "@/lib/api/apiHelper";
import { sendInviteMemberEmail } from "@/lib/email";
import { prisma } from "@formbricks/database";
@@ -20,7 +21,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
return res.status(403).json({ message: "Not authorized" });
}
if (process.env.NEXT_PUBLIC_INVITE_DISABLED === "1") {
if (env.NEXT_PUBLIC_INVITE_DISABLED === "1") {
return res.status(403).json({ message: "Invite Disabled" });
}

35
pnpm-lock.yaml generated
View File

@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
@@ -162,9 +166,6 @@ importers:
'@json2csv/node':
specifier: ^7.0.1
version: 7.0.1
'@next/env':
specifier: ^13.4.8
version: 13.4.8
'@paralleldrive/cuid2':
specifier: ^2.2.1
version: 2.2.1
@@ -174,6 +175,9 @@ importers:
'@radix-ui/react-dropdown-menu':
specifier: ^2.0.5
version: 2.0.5(react-dom@18.2.0)(react@18.2.0)
'@t3-oss/env-nextjs':
specifier: ^0.6.0
version: 0.6.0(typescript@5.1.6)(zod@3.21.4)
bcryptjs:
specifier: ^2.4.3
version: 2.4.3
@@ -5149,6 +5153,27 @@ packages:
dependencies:
defer-to-connect: 1.1.3
/@t3-oss/env-core@0.6.0(typescript@5.1.6)(zod@3.21.4):
resolution: {integrity: sha512-3FkPAba069WRZVVab/sB1m3eSGn/rZeypx5k+sWEu1d+k0OQdRDnvFS+7MtxYgqVrwaRk3b7yVnX2dgSPVmWPQ==}
peerDependencies:
typescript: '>=4.7.2'
zod: ^3.0.0
dependencies:
typescript: 5.1.6
zod: 3.21.4
dev: false
/@t3-oss/env-nextjs@0.6.0(typescript@5.1.6)(zod@3.21.4):
resolution: {integrity: sha512-SpzcGNIbUYcQw4zPPFeRJqCC1560zL7QmB0puIqOnuCsmykPkqHPX+n9CNZLXVQerboHzfvb7Kd+jAdouk72Vw==}
peerDependencies:
typescript: '>=4.7.2'
zod: ^3.0.0
dependencies:
'@t3-oss/env-core': 0.6.0(typescript@5.1.6)(zod@3.21.4)
typescript: 5.1.6
zod: 3.21.4
dev: false
/@tailwindcss/forms@0.5.3(tailwindcss@3.3.2):
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
peerDependencies:
@@ -20726,7 +20751,3 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@@ -56,6 +56,8 @@
"RAILWAY_STATIC_URL",
"RENDER_EXTERNAL_URL",
"SENTRY_DSN",
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"TELEMETRY_DISABLED",
"VERCEL_URL"
]