mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 05:40:02 -06:00
chore: update version for 1.6 release (#2123)
This commit is contained in:
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -30,8 +30,5 @@ jobs:
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
|
||||
- name: Build formbricks-js dependencies
|
||||
run: pnpm build --filter=js
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
|
||||
@@ -103,7 +103,7 @@ Formbricks is both a free and open source survey platform - and a privacy-first
|
||||
- 🔒 [Auth.js](https://authjs.dev/)
|
||||
|
||||
- 🧘♂️ [Zod](https://zod.dev/)
|
||||
-
|
||||
|
||||
- 🐛 [Vitest](https://vitest.dev/)
|
||||
|
||||
<a id="getting-started"></a>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import CorsHandling from "./cors-handling-in-api.webp";
|
||||
|
||||
export const metadata = {
|
||||
@@ -25,7 +26,7 @@ Thank you for choosing to contribute to Formbricks. Before you start, please fam
|
||||
- Constants should be in the packages folder
|
||||
- Types should be in the packages folder
|
||||
- How we handle Pull Requests
|
||||
- Read environment variables from `.env.mjs`
|
||||
- Read server-side environment variables from `constants.ts`
|
||||
|
||||
---
|
||||
|
||||
@@ -83,9 +84,9 @@ You should store constants in `packages/lib/constants`
|
||||
|
||||
You should store type in `packages/types`
|
||||
|
||||
## Read environment variables from `.env.mjs`
|
||||
## Read server-side environment variables from `constants.ts`
|
||||
|
||||
Environment variables (`process.env`) shouldn’t be accessed directly but be added in the `.env.mjs` and should be accessed from here. This practice helps us ensure that the variables are typesafe.
|
||||
Server-side environment variables (`process.env`) shouldn’t be accessed directly but included into the `constants.ts` file and read from there. This way we can assure they are used only on the server side and are also type-safe.
|
||||
|
||||
## How we handle Pull Requests
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ All you need to do is copy a `<script>` tag to your HTML head, and that’s abou
|
||||
```html {{ title: 'index.html' }}
|
||||
<!-- START Formbricks Surveys -->
|
||||
<script type="text/javascript">
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.5.1/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "<your-environment-id>", apiHost: "<api-host>"})},500)}();
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.6.0/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "<your-environment-id>", apiHost: "<api-host>"})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->
|
||||
```
|
||||
@@ -397,20 +397,24 @@ Enabling Formbricks debug mode in your browser is a useful troubleshooting step
|
||||
To activate Formbricks debug mode:
|
||||
|
||||
1. **In Your Integration Code:**
|
||||
|
||||
- Locate the initialization code for Formbricks in your application (HTML, ReactJS, NextJS, VueJS).
|
||||
- Set the `debug` option to `true` when initializing Formbricks.
|
||||
|
||||
2. **View Debug Logs:**
|
||||
|
||||
- Open your browser's developer tools by pressing `F12` or right-clicking and selecting "Inspect."
|
||||
- Navigate to the "Console" tab to view Formbricks debugging information.
|
||||
|
||||
**How to Open Browser Console:**
|
||||
|
||||
- **Google Chrome:** Press `F12` or right-click, select "Inspect," and go to the "Console" tab.
|
||||
- **Firefox:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
|
||||
- **Safari:** Press `Option + Command + C` to open the developer tools and navigate to the "Console" tab.
|
||||
- **Edge:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
|
||||
|
||||
3. **Via URL Parameter:**
|
||||
|
||||
- For quick activation, add `?formbricksDebug=true` to your application's URL.
|
||||
|
||||
This parameter will enable debugging for the current session.
|
||||
@@ -418,6 +422,7 @@ To activate Formbricks debug mode:
|
||||
### Common Use Cases
|
||||
|
||||
Debug mode is beneficial for scenarios such as:
|
||||
|
||||
- Verifying Formbricks functionality.
|
||||
- Identifying integration issues.
|
||||
- Troubleshooting unexpected behavior.
|
||||
@@ -425,6 +430,7 @@ Debug mode is beneficial for scenarios such as:
|
||||
### Debug Log Messages
|
||||
|
||||
Specific debug log messages may provide insights into:
|
||||
|
||||
- API calls and responses.
|
||||
- Event tracking and form interactions.
|
||||
- Integration errors.
|
||||
|
||||
@@ -4,7 +4,7 @@ import { formbricksEnabled } from "@/app/lib/formbricks";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import formbricks from "@formbricks/js";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
type UsageAttributesUpdaterProps = {
|
||||
numSurveys: number;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Session } from "next-auth";
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { TUser, TUserObjective } from "@formbricks/types/user";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
|
||||
import { handleTabNavigation } from "../utils";
|
||||
|
||||
@@ -5,9 +5,8 @@ import { responses } from "@/app/lib/api/response";
|
||||
import { headers } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { UPLOADS_DIR } from "@formbricks/lib/constants";
|
||||
import { ENCRYPTION_KEY, UPLOADS_DIR } from "@formbricks/lib/constants";
|
||||
import { validateLocalSignedUrl } from "@formbricks/lib/crypto";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { putFileToLocalStorage } from "@formbricks/lib/storage/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
|
||||
@@ -91,7 +90,7 @@ export async function POST(req: NextRequest, context: Context): Promise<Response
|
||||
fileType,
|
||||
Number(signedTimestamp),
|
||||
signedSignature,
|
||||
env.ENCRYPTION_KEY
|
||||
ENCRYPTION_KEY
|
||||
);
|
||||
|
||||
if (!validated) {
|
||||
|
||||
@@ -7,9 +7,8 @@ import { headers } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { UPLOADS_DIR } from "@formbricks/lib/constants";
|
||||
import { ENCRYPTION_KEY, UPLOADS_DIR } from "@formbricks/lib/constants";
|
||||
import { validateLocalSignedUrl } from "@formbricks/lib/crypto";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { putFileToLocalStorage } from "@formbricks/lib/storage/service";
|
||||
|
||||
@@ -72,7 +71,7 @@ export async function POST(req: NextRequest): Promise<Response> {
|
||||
fileType,
|
||||
Number(signedTimestamp),
|
||||
signedSignature,
|
||||
env.ENCRYPTION_KEY
|
||||
ENCRYPTION_KEY
|
||||
);
|
||||
|
||||
if (!validated) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { prisma } from "@formbricks/database";
|
||||
import {
|
||||
DEFAULT_TEAM_ID,
|
||||
DEFAULT_TEAM_ROLE,
|
||||
EMAIL_AUTH_ENABLED,
|
||||
EMAIL_VERIFICATION_DISABLED,
|
||||
INVITE_DISABLED,
|
||||
SIGNUP_ENABLED,
|
||||
} from "@formbricks/lib/constants";
|
||||
import { sendInviteAcceptedEmail, sendVerificationEmail } from "@formbricks/lib/emails/emails";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { deleteInvite } from "@formbricks/lib/invite/service";
|
||||
import { verifyInviteToken } from "@formbricks/lib/jwt";
|
||||
import { createMembership } from "@formbricks/lib/membership/service";
|
||||
@@ -63,16 +64,16 @@ export async function POST(request: Request) {
|
||||
|
||||
// User signs up without invite
|
||||
// Default team assignment is enabled
|
||||
if (env.DEFAULT_TEAM_ID && env.DEFAULT_TEAM_ID.length > 0) {
|
||||
if (DEFAULT_TEAM_ID && DEFAULT_TEAM_ID.length > 0) {
|
||||
// check if team exists
|
||||
let team = await getTeam(env.DEFAULT_TEAM_ID);
|
||||
let team = await getTeam(DEFAULT_TEAM_ID);
|
||||
let isNewTeam = false;
|
||||
if (!team) {
|
||||
// create team with id from env
|
||||
team = await createTeam({ id: env.DEFAULT_TEAM_ID, name: user.name + "'s Team" });
|
||||
team = await createTeam({ id: DEFAULT_TEAM_ID, name: user.name + "'s Team" });
|
||||
isNewTeam = true;
|
||||
}
|
||||
const role = isNewTeam ? "owner" : env.DEFAULT_TEAM_ROLE || "admin";
|
||||
const role = isNewTeam ? "owner" : DEFAULT_TEAM_ROLE || "admin";
|
||||
await createMembership(team.id, user.id, { role, accepted: true });
|
||||
}
|
||||
// Without default team assignment
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import formbricks from "@formbricks/js";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
export const formbricksEnabled =
|
||||
typeof env.NEXT_PUBLIC_FORMBRICKS_API_HOST && env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import cuid2 from "@paralleldrive/cuid2";
|
||||
|
||||
import { ENCRYPTION_KEY, FORMBRICKS_ENCRYPTION_KEY } from "@formbricks/lib/constants";
|
||||
import { decryptAES128, symmetricDecrypt, symmetricEncrypt } from "@formbricks/lib/crypto";
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
|
||||
// generate encrypted single use id for the survey
|
||||
export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
|
||||
@@ -10,7 +10,7 @@ export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
|
||||
return cuid;
|
||||
}
|
||||
|
||||
const encryptedCuid = symmetricEncrypt(cuid, env.ENCRYPTION_KEY);
|
||||
const encryptedCuid = symmetricEncrypt(cuid, ENCRYPTION_KEY);
|
||||
return encryptedCuid;
|
||||
};
|
||||
|
||||
@@ -20,13 +20,13 @@ export const validateSurveySingleUseId = (surveySingleUseId: string): string | u
|
||||
let decryptedCuid: string | null = null;
|
||||
|
||||
if (surveySingleUseId.length === 64) {
|
||||
if (!env.FORMBRICKS_ENCRYPTION_KEY) {
|
||||
if (!FORMBRICKS_ENCRYPTION_KEY) {
|
||||
throw new Error("FORMBRICKS_ENCRYPTION_KEY is not defined");
|
||||
}
|
||||
|
||||
decryptedCuid = decryptAES128(env.FORMBRICKS_ENCRYPTION_KEY!, surveySingleUseId);
|
||||
decryptedCuid = decryptAES128(FORMBRICKS_ENCRYPTION_KEY!, surveySingleUseId);
|
||||
} else {
|
||||
decryptedCuid = symmetricDecrypt(surveySingleUseId, env.ENCRYPTION_KEY);
|
||||
decryptedCuid = symmetricDecrypt(surveySingleUseId, ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
if (cuid2.isCuid(decryptedCuid)) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { withSentryConfig } from "@sentry/nextjs";
|
||||
import createJiti from "jiti";
|
||||
|
||||
import "@formbricks/lib/env.mjs";
|
||||
const jiti = createJiti(new URL(import.meta.url).pathname);
|
||||
|
||||
jiti("@formbricks/lib/env");
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
@@ -26,7 +26,6 @@
|
||||
"@json2csv/node": "^7.0.6",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@react-email/components": "^0.0.15",
|
||||
"@sentry/nextjs": "^7.102.1",
|
||||
"@vercel/og": "^0.6.2",
|
||||
@@ -34,17 +33,18 @@
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"encoding": "^0.1.13",
|
||||
"framer-motion": "11.0.5",
|
||||
"framer-motion": "11.0.6",
|
||||
"googleapis": "^133.0.0",
|
||||
"jiti": "^1.21.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^10.2.0",
|
||||
"lucide-react": "^0.336.0",
|
||||
"lucide-react": "^0.339.0",
|
||||
"mime": "^4.0.1",
|
||||
"next": "14.1.0",
|
||||
"nodemailer": "^6.9.10",
|
||||
"otplib": "^12.0.1",
|
||||
"posthog-js": "^1.108.2",
|
||||
"posthog-js": "^1.108.3",
|
||||
"prismjs": "^1.29.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "18.2.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/api",
|
||||
"license": "MIT",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"description": "Formbricks-api is an api wrapper for the Formbricks client API",
|
||||
"keywords": [
|
||||
"Formbricks",
|
||||
@@ -32,12 +32,15 @@
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "1.10.12",
|
||||
"terser": "^5.27.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-dts": "^3.7.2"
|
||||
"terser": "^5.28.1",
|
||||
"vite": "^5.1.4",
|
||||
"vite-plugin-dts": "^3.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,20 +23,22 @@
|
||||
"lint": "eslint ./src --fix",
|
||||
"post-install": "pnpm generate",
|
||||
"predev": "pnpm generate",
|
||||
"data-migration:1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts"
|
||||
"data-migration:v1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.8.1",
|
||||
"@prisma/extension-accelerate": "^0.6.2",
|
||||
"@prisma/client": "^5.10.2",
|
||||
"@prisma/extension-accelerate": "^0.6.3",
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"dotenv-cli": "^7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"prisma": "^5.8.1",
|
||||
"prisma-dbml-generator": "^0.10.0",
|
||||
"prisma-json-types-generator": "^3.0.3",
|
||||
"prisma": "^5.10.2",
|
||||
"prisma-dbml-generator": "^0.12.0",
|
||||
"prisma-json-types-generator": "^3.0.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"zod": "^3.22.4",
|
||||
"zod-prisma": "^0.5.4"
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { DeepMockProxy, mockDeep, mockReset } from "jest-mock-extended";
|
||||
|
||||
import { prisma } from "./client";
|
||||
|
||||
jest.mock("./client", () => ({
|
||||
__esModule: true,
|
||||
prisma: mockDeep<PrismaClient>(),
|
||||
}));
|
||||
|
||||
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(prismaMock);
|
||||
});
|
||||
@@ -1,14 +1,16 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
import { handleCheckoutSessionCompleted } from "../handlers/checkoutSessionCompleted";
|
||||
import { handleSubscriptionUpdatedOrCreated } from "../handlers/subscriptionCreatedOrUpdated";
|
||||
import { handleSubscriptionDeleted } from "../handlers/subscriptionDeleted";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET!;
|
||||
const webhookSecret: string = env.STRIPE_WEBHOOK_SECRET!;
|
||||
|
||||
const webhookHandler = async (requestBody: string, stripeSignature: string) => {
|
||||
let event: Stripe.Event;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import {
|
||||
getMonthlyActiveTeamPeopleCount,
|
||||
getMonthlyTeamResponseCount,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants";
|
||||
import { reportUsage } from "../lib/reportUsage";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import {
|
||||
getMonthlyActiveTeamPeopleCount,
|
||||
getMonthlyTeamResponseCount,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants";
|
||||
import { reportUsage } from "../lib/reportUsage";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { getTeam, updateTeam } from "@formbricks/lib/team/service";
|
||||
|
||||
import { ProductFeatureKeys, StripeProductNames } from "../lib/constants";
|
||||
import { unsubscribeCoreAndAppSurveyFeatures, unsubscribeLinkSurveyProFeatures } from "../lib/downgradePlan";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { getTeam } from "@formbricks/lib/team/service";
|
||||
|
||||
import { StripePriceLookupKeys } from "./constants";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { getTeam, updateTeam } from "@formbricks/lib/team/service";
|
||||
|
||||
import { StripePriceLookupKeys } from "./constants";
|
||||
import { getFirstOfNextMonthTimestamp } from "./createSubscription";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
import { ProductFeatureKeys } from "./constants";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2023-10-16",
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"stripe": "^14.13.0"
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"stripe": "^14.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@
|
||||
"clean": "rimraf node_modules .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "^14.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "1.10.12",
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"eslint-plugin-storybook": "^0.6.15"
|
||||
"eslint-plugin-storybook": "^0.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = (api) => {
|
||||
return {
|
||||
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
|
||||
plugins: [["@babel/plugin-transform-react-jsx", { runtime: api.env("test") ? "automatic" : "classic" }]],
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/js",
|
||||
"license": "MIT",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.0",
|
||||
"description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"repository": {
|
||||
@@ -35,44 +35,29 @@
|
||||
"build:dev": "tsc && vite build --mode dev",
|
||||
"go": "vite build --watch --mode dev",
|
||||
"lint": "eslint ./src --fix",
|
||||
"test": "jest --coverage --no-cache",
|
||||
"clean": "rimraf .turbo node_modules dist coverage"
|
||||
},
|
||||
"author": "Formbricks <hola@formbricks.com>",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.7",
|
||||
"@babel/preset-env": "^7.23.8",
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@formbricks/api": "workspace:*",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/surveys": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||
"@typescript-eslint/parser": "^6.19.1",
|
||||
"babel-jest": "^29.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"@typescript-eslint/parser": "^7.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"terser": "^5.27.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-dts": "^3.7.2",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "1.10.12"
|
||||
"eslint-config-turbo": "1.10.12",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"terser": "^5.28.1",
|
||||
"vite": "^5.1.4",
|
||||
"vite-plugin-dts": "^3.7.3"
|
||||
},
|
||||
"jest": {
|
||||
"transformIgnorePatterns": [
|
||||
"!node_modules/"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/tests/__mocks__/setupTests.js"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tests/__mocks__/fileMock.js",
|
||||
"\\.(css|less)$": "<rootDir>/tests/__mocks__/styleMock.js"
|
||||
}
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
import fetchMock from "jest-fetch-mock";
|
||||
|
||||
import { constants } from "../constants";
|
||||
|
||||
const {
|
||||
environmentId,
|
||||
sessionId,
|
||||
expiryTime,
|
||||
surveyId,
|
||||
questionOneId,
|
||||
questionTwoId,
|
||||
choiceOneId,
|
||||
choiceTwoId,
|
||||
choiceThreeId,
|
||||
initialPersonUid,
|
||||
initialUserId,
|
||||
initialUserEmail,
|
||||
newPersonUid,
|
||||
eventIdForRouteChange,
|
||||
updatedUserEmail,
|
||||
customAttributeKey,
|
||||
customAttributeValue,
|
||||
eventIdForEventTracking,
|
||||
} = constants;
|
||||
|
||||
export const mockInitResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
person: {
|
||||
id: initialPersonUid,
|
||||
createdAt: "2021-03-09T15:00:00.000Z",
|
||||
updatedAt: "2021-03-09T15:00:00.000Z",
|
||||
attributes: {},
|
||||
},
|
||||
surveys: [
|
||||
{
|
||||
id: surveyId,
|
||||
questions: [
|
||||
{
|
||||
id: questionOneId,
|
||||
type: "multipleChoiceSingle",
|
||||
choices: [
|
||||
{
|
||||
id: choiceOneId,
|
||||
label: "Not at all disappointed",
|
||||
},
|
||||
{
|
||||
id: choiceTwoId,
|
||||
label: "Somewhat disappointed",
|
||||
},
|
||||
{
|
||||
id: choiceThreeId,
|
||||
label: "Very disappointed",
|
||||
},
|
||||
],
|
||||
headline: "How disappointed would you be if you could no longer use Test-Formbricks?",
|
||||
required: true,
|
||||
subheader: "Please select one of the following options:",
|
||||
},
|
||||
{
|
||||
id: questionTwoId,
|
||||
type: "openText",
|
||||
headline: "How can we improve Test-Formbricks for you?",
|
||||
required: true,
|
||||
subheader: "Please be as specific as possible.",
|
||||
},
|
||||
],
|
||||
triggers: [],
|
||||
thankYouCard: {
|
||||
enabled: true,
|
||||
headline: "Thank you!",
|
||||
subheader: "We appreciate your feedback.",
|
||||
},
|
||||
autoClose: null,
|
||||
delay: 0,
|
||||
},
|
||||
],
|
||||
noCodeActionClasses: [],
|
||||
product: {
|
||||
noCodeEvents: [],
|
||||
brandColor: "#20b398",
|
||||
linkSurveyBranding: true,
|
||||
inAppBranding: true,
|
||||
placement: "bottomRight",
|
||||
darkOverlay: false,
|
||||
clickOutsideClose: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const mockSetUserIdResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
surveys: [],
|
||||
session: {
|
||||
id: sessionId,
|
||||
createdAt: "2021-03-09T15:00:00.000Z",
|
||||
updatedAt: "2021-03-09T15:00:00.000Z",
|
||||
expiresAt: expiryTime,
|
||||
},
|
||||
noCodeActionClasses: [],
|
||||
person: {
|
||||
id: initialPersonUid,
|
||||
environmentId,
|
||||
attributes: { userId: initialUserId },
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const mockSetEmailIdResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: initialPersonUid,
|
||||
environmentId,
|
||||
attributes: { userId: initialUserId, email: initialUserEmail },
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const mockSetCustomAttributeResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: initialPersonUid,
|
||||
environmentId,
|
||||
attributes: {
|
||||
userId: initialUserId,
|
||||
email: initialUserEmail,
|
||||
[customAttributeKey]: customAttributeValue,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const mockUpdateEmailResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: initialPersonUid,
|
||||
environmentId,
|
||||
attributes: {
|
||||
userId: initialUserId,
|
||||
email: updatedUserEmail,
|
||||
[customAttributeKey]: customAttributeValue,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const mockEventTrackResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
id: eventIdForEventTracking,
|
||||
})
|
||||
);
|
||||
console.log('Formbricks: Event "Button Clicked" tracked');
|
||||
};
|
||||
|
||||
export const mockRefreshResponse = () => {
|
||||
fetchMock.mockResponseOnce(JSON.stringify({}));
|
||||
console.log("Settings refreshed");
|
||||
};
|
||||
|
||||
export const mockRegisterRouteChangeResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
id: eventIdForRouteChange,
|
||||
})
|
||||
);
|
||||
console.log("Checking page url: http://localhost/");
|
||||
};
|
||||
|
||||
export const mockResetResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
surveys: [],
|
||||
person: {
|
||||
id: newPersonUid,
|
||||
environmentId,
|
||||
attributes: [],
|
||||
},
|
||||
session: {
|
||||
id: sessionId,
|
||||
createdAt: "2021-03-09T15:00:00.000Z",
|
||||
updatedAt: "2021-03-09T15:00:00.000Z",
|
||||
expiresAt: expiryTime,
|
||||
},
|
||||
noCodeActionClasses: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
console.log("Resetting person. Getting new person, session and settings from backend");
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = 'placeholer-to-not-mock-all-files-as-js';
|
||||
@@ -1,10 +0,0 @@
|
||||
/** @type {import('jest').Config} */
|
||||
const config = {
|
||||
verbose: true,
|
||||
testEnvironment: "jsdom"
|
||||
};
|
||||
|
||||
import fetchMock from "jest-fetch-mock";
|
||||
fetchMock.enableMocks();
|
||||
|
||||
module.exports = config;
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = {};
|
||||
@@ -1,59 +0,0 @@
|
||||
const generateUserId = () => {
|
||||
const min = 1000;
|
||||
const max = 9999;
|
||||
const randomNum = Math.floor(Math.random() * (max - min + 1) + min);
|
||||
return randomNum.toString();
|
||||
};
|
||||
|
||||
const generateEmailId = () => {
|
||||
const domain = "formbricks.test";
|
||||
const randomString = Math.random().toString(36).substring(2);
|
||||
const emailId = `${randomString}@${domain}`;
|
||||
return emailId;
|
||||
};
|
||||
|
||||
const generateRandomString = () => {
|
||||
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const maxLength = 8;
|
||||
|
||||
let randomString = "";
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
randomString += characters.charAt(randomIndex);
|
||||
}
|
||||
return randomString;
|
||||
};
|
||||
|
||||
const getOneDayExpiryTime = () => {
|
||||
var ms = new Date().getTime();
|
||||
var oneDayMs = 24 * 60 * 60 * 1000; // Number of milliseconds in one day
|
||||
var expiryOfOneDay = ms + oneDayMs;
|
||||
return expiryOfOneDay;
|
||||
};
|
||||
|
||||
export const constants = {
|
||||
environmentId: "mockedEnvironmentId",
|
||||
apiHost: "mockedApiHost",
|
||||
sessionId: generateRandomString(),
|
||||
expiryTime: getOneDayExpiryTime(),
|
||||
surveyId: generateRandomString(),
|
||||
questionOneId: generateRandomString(),
|
||||
questionTwoId: generateRandomString(),
|
||||
choiceOneId: generateRandomString(),
|
||||
choiceTwoId: generateRandomString(),
|
||||
choiceThreeId: generateRandomString(),
|
||||
choiceFourId: generateRandomString(),
|
||||
initialPersonUid: generateRandomString(),
|
||||
newPersonUid: generateRandomString(),
|
||||
initialUserId: generateUserId(),
|
||||
initialUserEmail: generateEmailId(),
|
||||
updatedUserEmail: generateEmailId(),
|
||||
customAttributeKey: generateRandomString(),
|
||||
customAttributeValue: generateRandomString(),
|
||||
userIdAttributeId: generateRandomString(),
|
||||
userInitialEmailAttributeId: generateRandomString(),
|
||||
userCustomAttrAttributeId: generateRandomString(),
|
||||
userUpdatedEmailAttributeId: generateRandomString(),
|
||||
eventIdForEventTracking: generateRandomString(),
|
||||
eventIdForRouteChange: generateRandomString(),
|
||||
} as const;
|
||||
@@ -1,140 +0,0 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import {
|
||||
mockEventTrackResponse,
|
||||
mockInitResponse,
|
||||
mockRegisterRouteChangeResponse,
|
||||
mockResetResponse,
|
||||
mockSetCustomAttributeResponse,
|
||||
mockSetEmailIdResponse,
|
||||
mockSetUserIdResponse,
|
||||
mockUpdateEmailResponse,
|
||||
} from "./__mocks__/apiMock";
|
||||
|
||||
import { TPersonAttributes } from "@formbricks/types/people";
|
||||
|
||||
import formbricks from "../src/index";
|
||||
import { constants } from "./constants";
|
||||
|
||||
const consoleLogMock = jest.spyOn(console, "log").mockImplementation();
|
||||
|
||||
test("Test Jest", () => {
|
||||
expect(1 + 9).toBe(10);
|
||||
});
|
||||
|
||||
const {
|
||||
environmentId,
|
||||
apiHost,
|
||||
initialUserId,
|
||||
initialUserEmail,
|
||||
updatedUserEmail,
|
||||
customAttributeKey,
|
||||
customAttributeValue,
|
||||
} = constants;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.resetMocks();
|
||||
});
|
||||
|
||||
/*
|
||||
test("Formbricks should Initialise", async () => {
|
||||
mockInitResponse();
|
||||
|
||||
await formbricks.init({
|
||||
environmentId,
|
||||
apiHost,
|
||||
userId: initialUserId,
|
||||
});
|
||||
|
||||
const configFromBrowser = localStorage.getItem("formbricks-js");
|
||||
expect(configFromBrowser).toBeTruthy();
|
||||
|
||||
if (configFromBrowser) {
|
||||
const jsonSavedConfig = JSON.parse(configFromBrowser);
|
||||
expect(jsonSavedConfig.environmentId).toStrictEqual(environmentId);
|
||||
expect(jsonSavedConfig.apiHost).toStrictEqual(apiHost);
|
||||
}
|
||||
});
|
||||
|
||||
test("Formbricks should set email", async () => {
|
||||
mockSetEmailIdResponse();
|
||||
await formbricks.setEmail(initialUserEmail);
|
||||
|
||||
const currentStatePerson = formbricks.getPerson();
|
||||
|
||||
const currentStatePersonAttributes = currentStatePerson.attributes;
|
||||
const numberOfUserAttributes = Object.keys(currentStatePersonAttributes).length;
|
||||
expect(numberOfUserAttributes).toStrictEqual(2);
|
||||
|
||||
const userId = currentStatePersonAttributes.userId;
|
||||
expect(userId).toStrictEqual(initialUserId);
|
||||
const email = currentStatePersonAttributes.email;
|
||||
expect(email).toStrictEqual(initialUserEmail);
|
||||
});
|
||||
|
||||
test("Formbricks should set custom attribute", async () => {
|
||||
mockSetCustomAttributeResponse();
|
||||
await formbricks.setAttribute(customAttributeKey, customAttributeValue);
|
||||
|
||||
const currentStatePerson = formbricks.getPerson();
|
||||
|
||||
const currentStatePersonAttributes = currentStatePerson.attributes;
|
||||
const numberOfUserAttributes = Object.keys(currentStatePersonAttributes).length;
|
||||
expect(numberOfUserAttributes).toStrictEqual(3);
|
||||
|
||||
const userId = currentStatePersonAttributes.userId;
|
||||
expect(userId).toStrictEqual(initialUserId);
|
||||
const email = currentStatePersonAttributes.email;
|
||||
expect(email).toStrictEqual(initialUserEmail);
|
||||
const customAttribute = currentStatePersonAttributes[customAttributeKey];
|
||||
expect(customAttribute).toStrictEqual(customAttributeValue);
|
||||
});
|
||||
|
||||
test("Formbricks should update attribute", async () => {
|
||||
mockUpdateEmailResponse();
|
||||
await formbricks.setEmail(updatedUserEmail);
|
||||
|
||||
const currentStatePerson = formbricks.getPerson();
|
||||
|
||||
const currentStatePersonAttributes = currentStatePerson.attributes;
|
||||
|
||||
const numberOfUserAttributes = Object.keys(currentStatePersonAttributes).length;
|
||||
expect(numberOfUserAttributes).toStrictEqual(3);
|
||||
|
||||
const userId = currentStatePersonAttributes.userId;
|
||||
expect(userId).toStrictEqual(initialUserId);
|
||||
const email = currentStatePersonAttributes.email;
|
||||
expect(email).toStrictEqual(updatedUserEmail);
|
||||
const customAttribute = currentStatePersonAttributes[customAttributeKey];
|
||||
expect(customAttribute).toStrictEqual(customAttributeValue);
|
||||
});
|
||||
|
||||
test("Formbricks should track event", async () => {
|
||||
mockEventTrackResponse();
|
||||
const mockButton = document.createElement("button");
|
||||
mockButton.addEventListener("click", async () => {
|
||||
await formbricks.track("Button Clicked");
|
||||
});
|
||||
await mockButton.click();
|
||||
expect(consoleLogMock).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/Formbricks: Event "Button Clicked" tracked/)
|
||||
);
|
||||
});
|
||||
|
||||
test("Formbricks should register for route change", async () => {
|
||||
mockRegisterRouteChangeResponse();
|
||||
await formbricks.registerRouteChange();
|
||||
expect(consoleLogMock).toHaveBeenCalledWith(expect.stringMatching(/Checking page url/));
|
||||
});
|
||||
|
||||
test("Formbricks should reset", async () => {
|
||||
mockResetResponse();
|
||||
await formbricks.reset();
|
||||
const currentStatePerson = formbricks.getPerson();
|
||||
const currentStatePersonAttributes = currentStatePerson.attributes;
|
||||
|
||||
expect(Object.keys(currentStatePersonAttributes).length).toBe(0);
|
||||
});
|
||||
|
||||
*/
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface Attribute {
|
||||
id: string;
|
||||
value: string;
|
||||
attributeClass: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
14
packages/lib/__mocks__/database.ts
Normal file
14
packages/lib/__mocks__/database.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { beforeEach, vi } from "vitest";
|
||||
import { mockDeep, mockReset } from "vitest-mock-extended";
|
||||
|
||||
export const prisma = mockDeep<PrismaClient>();
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
__esModule: true,
|
||||
prisma,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(prisma);
|
||||
});
|
||||
@@ -10,14 +10,22 @@ import { prisma } from "@formbricks/database";
|
||||
import { createAccount } from "./account/service";
|
||||
import { verifyPassword } from "./auth/util";
|
||||
import {
|
||||
AZUREAD_CLIENT_ID,
|
||||
AZUREAD_CLIENT_SECRET,
|
||||
AZUREAD_TENANT_ID,
|
||||
DEFAULT_TEAM_ID,
|
||||
DEFAULT_TEAM_ROLE,
|
||||
EMAIL_VERIFICATION_DISABLED,
|
||||
GITHUB_ID,
|
||||
GITHUB_SECRET,
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
OIDC_CLIENT_ID,
|
||||
OIDC_CLIENT_SECRET,
|
||||
OIDC_DISPLAY_NAME,
|
||||
OIDC_ISSUER,
|
||||
OIDC_SIGNING_ALGORITHM,
|
||||
} from "./constants";
|
||||
import { env } from "./env.mjs";
|
||||
import { verifyToken } from "./jwt";
|
||||
import { createMembership } from "./membership/service";
|
||||
import { createProduct } from "./product/service";
|
||||
@@ -125,18 +133,18 @@ export const authOptions: NextAuthOptions = {
|
||||
},
|
||||
}),
|
||||
GitHubProvider({
|
||||
clientId: env.GITHUB_ID || "",
|
||||
clientSecret: env.GITHUB_SECRET || "",
|
||||
clientId: GITHUB_ID || "",
|
||||
clientSecret: GITHUB_SECRET || "",
|
||||
}),
|
||||
GoogleProvider({
|
||||
clientId: env.GOOGLE_CLIENT_ID || "",
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET || "",
|
||||
clientId: GOOGLE_CLIENT_ID || "",
|
||||
clientSecret: GOOGLE_CLIENT_SECRET || "",
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
}),
|
||||
AzureAD({
|
||||
clientId: env.AZUREAD_CLIENT_ID || "",
|
||||
clientSecret: env.AZUREAD_CLIENT_SECRET || "",
|
||||
tenantId: env.AZUREAD_TENANT_ID || "",
|
||||
clientId: AZUREAD_CLIENT_ID || "",
|
||||
clientSecret: AZUREAD_CLIENT_SECRET || "",
|
||||
tenantId: AZUREAD_TENANT_ID || "",
|
||||
}),
|
||||
{
|
||||
id: "openid",
|
||||
@@ -252,16 +260,16 @@ export const authOptions: NextAuthOptions = {
|
||||
});
|
||||
|
||||
// Default team assignment if env variable is set
|
||||
if (env.DEFAULT_TEAM_ID && env.DEFAULT_TEAM_ID.length > 0) {
|
||||
if (DEFAULT_TEAM_ID && DEFAULT_TEAM_ID.length > 0) {
|
||||
// check if team exists
|
||||
let team = await getTeam(env.DEFAULT_TEAM_ID);
|
||||
let team = await getTeam(DEFAULT_TEAM_ID);
|
||||
let isNewTeam = false;
|
||||
if (!team) {
|
||||
// create team with id from env
|
||||
team = await createTeam({ id: env.DEFAULT_TEAM_ID, name: userProfile.name + "'s Team" });
|
||||
team = await createTeam({ id: DEFAULT_TEAM_ID, name: userProfile.name + "'s Team" });
|
||||
isNewTeam = true;
|
||||
}
|
||||
const role = isNewTeam ? "owner" : env.DEFAULT_TEAM_ROLE || "admin";
|
||||
const role = isNewTeam ? "owner" : DEFAULT_TEAM_ROLE || "admin";
|
||||
await createMembership(team.id, userProfile.id, { role, accepted: true });
|
||||
await createAccount({
|
||||
...account,
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { presets: ["@babel/preset-env"] };
|
||||
module.exports = { presets: [["@babel/preset-env", { targets: { node: "current" } }]] };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
|
||||
import { env } from "./env.mjs";
|
||||
import { env } from "./env";
|
||||
|
||||
export const IS_FORMBRICKS_CLOUD = env.IS_FORMBRICKS_CLOUD === "1";
|
||||
export const REVALIDATION_INTERVAL = 0; //TODO: find a good way to cache and revalidate data when it changes
|
||||
@@ -80,10 +80,14 @@ export const RESPONSES_PER_PAGE = 10;
|
||||
export const TEXT_RESPONSES_PER_PAGE = 5;
|
||||
|
||||
export const DEFAULT_TEAM_ID = env.DEFAULT_TEAM_ID;
|
||||
export const DEFAULT_TEAM_ROLE = env.DEFAULT_TEAM_ROLE || "";
|
||||
export const DEFAULT_TEAM_ROLE = env.DEFAULT_TEAM_ROLE;
|
||||
export const ONBOARDING_DISABLED = env.ONBOARDING_DISABLED;
|
||||
|
||||
// Storage constants
|
||||
export const S3_ACCESS_KEY = env.S3_ACCESS_KEY;
|
||||
export const S3_SECRET_KEY = env.S3_SECRET_KEY;
|
||||
export const S3_REGION = env.S3_REGION;
|
||||
export const S3_BUCKET_NAME = env.S3_BUCKET_NAME;
|
||||
export const UPLOADS_DIR = "./uploads";
|
||||
export const MAX_SIZES = {
|
||||
public: 1024 * 1024 * 10, // 10MB
|
||||
@@ -148,9 +152,12 @@ export const SYNC_USER_IDENTIFICATION_RATE_LIMIT = {
|
||||
allowedPerInterval: 5,
|
||||
};
|
||||
|
||||
export const DEBUG = process.env.DEBUG === "1";
|
||||
export const DEBUG = env.DEBUG === "1";
|
||||
|
||||
// Enterprise License constant
|
||||
export const ENTERPRISE_LICENSE_KEY = env.ENTERPRISE_LICENSE_KEY;
|
||||
|
||||
export const RATE_LIMITING_DISABLED = env.RATE_LIMITING_DISABLED === "1";
|
||||
|
||||
export const CUSTOMER_IO_SITE_ID = env.CUSTOMER_IO_SITE_ID;
|
||||
export const CUSTOMER_IO_API_KEY = env.CUSTOMER_IO_API_KEY;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
|
||||
import { env } from "./env.mjs";
|
||||
import { CUSTOMER_IO_API_KEY, CUSTOMER_IO_SITE_ID } from "./constants";
|
||||
|
||||
export const createCustomerIoCustomer = async (user: TUser) => {
|
||||
if (!env.CUSTOMER_IO_SITE_ID || !env.CUSTOMER_IO_API_KEY) {
|
||||
if (!CUSTOMER_IO_SITE_ID || !CUSTOMER_IO_API_KEY) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const auth = Buffer.from(`${env.CUSTOMER_IO_SITE_ID}:${env.CUSTOMER_IO_API_KEY}`).toString("base64");
|
||||
const auth = Buffer.from(`${CUSTOMER_IO_SITE_ID}:${CUSTOMER_IO_API_KEY}`).toString("base64");
|
||||
const res = await fetch(`https://track-eu.customer.io/api/v1/customers/${user.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
|
||||
@@ -27,7 +27,7 @@ import { formatDateFields } from "../utils/datetime";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { displayCache } from "./cache";
|
||||
|
||||
const selectDisplay = {
|
||||
export const selectDisplay = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {
|
||||
TDisplay,
|
||||
TDisplayCreateInput,
|
||||
TDisplayLegacyCreateInput,
|
||||
TDisplayLegacyUpdateInput,
|
||||
TDisplayUpdateInput,
|
||||
} from "@formbricks/types/displays";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
|
||||
import { selectDisplay } from "../../service";
|
||||
|
||||
export const mockEnvironmentId = "clqkr5961000108jyfnjmbjhi";
|
||||
export const mockSingleUseId = "qj57j3opsw8b5sxgea20fgcq";
|
||||
@@ -28,49 +26,51 @@ function createMockDisplay(overrides = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export const mockDisplay: TDisplay = createMockDisplay();
|
||||
export const mockDisplay = createMockDisplay();
|
||||
|
||||
export const mockDisplayWithPersonId: TDisplay = createMockDisplay({ personId: mockPersonId });
|
||||
export const mockDisplayWithPersonId = createMockDisplay({ personId: mockPersonId });
|
||||
|
||||
export const mockDisplayWithResponseId: TDisplay = createMockDisplay({
|
||||
export const mockDisplayWithResponseId = createMockDisplay({
|
||||
personId: mockPersonId,
|
||||
responseId: mockResponseId,
|
||||
});
|
||||
|
||||
export const mockDisplayInput: TDisplayCreateInput = {
|
||||
export const mockDisplayInput = {
|
||||
environmentId: mockEnvironmentId,
|
||||
surveyId: mockSurveyId,
|
||||
};
|
||||
export const mockDisplayInputWithUserId: TDisplayCreateInput = {
|
||||
export const mockDisplayInputWithUserId = {
|
||||
...mockDisplayInput,
|
||||
userId: mockUserId,
|
||||
};
|
||||
export const mockDisplayInputWithResponseId: TDisplayCreateInput = {
|
||||
export const mockDisplayInputWithResponseId = {
|
||||
...mockDisplayInputWithUserId,
|
||||
responseId: mockResponseId,
|
||||
};
|
||||
|
||||
export const mockDisplayLegacyInput: TDisplayLegacyCreateInput = {
|
||||
export const mockDisplayLegacyInput = {
|
||||
responseId: mockResponseId,
|
||||
surveyId: mockSurveyId,
|
||||
};
|
||||
export const mockDisplayLegacyInputWithPersonId: TDisplayLegacyCreateInput = {
|
||||
export const mockDisplayLegacyInputWithPersonId = {
|
||||
...mockDisplayLegacyInput,
|
||||
personId: mockPersonId,
|
||||
};
|
||||
|
||||
export const mockDisplayUpdate: TDisplayUpdateInput = {
|
||||
export const mockDisplayUpdate = {
|
||||
environmentId: mockEnvironmentId,
|
||||
userId: mockUserId,
|
||||
responseId: mockResponseId,
|
||||
};
|
||||
|
||||
export const mockDisplayLegacyUpdateInput: TDisplayLegacyUpdateInput = {
|
||||
export const mockDisplayLegacyUpdateInput = {
|
||||
personId: mockPersonId,
|
||||
responseId: mockResponseId,
|
||||
};
|
||||
|
||||
export const mockDisplayLegacyWithRespondedStatus: TDisplay = {
|
||||
export const mockDisplayLegacyWithRespondedStatus: Prisma.DisplayGetPayload<{
|
||||
select: typeof selectDisplay;
|
||||
}> = {
|
||||
...mockDisplayWithPersonId,
|
||||
status: "responded",
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { prisma } from "../../__mocks__/database";
|
||||
import { mockPerson } from "../../response/tests/__mocks__/data.mock";
|
||||
import {
|
||||
mockDisplay,
|
||||
@@ -15,8 +16,8 @@ import {
|
||||
} from "./__mocks__/data.mock";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { prismaMock } from "@formbricks/database/src/jestClient";
|
||||
import { DatabaseError, ValidationError } from "@formbricks/types/errors";
|
||||
|
||||
import {
|
||||
@@ -31,6 +32,15 @@ import {
|
||||
updateDisplayLegacy,
|
||||
} from "../service";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const testInputValidation = async (service: Function, ...args: any[]): Promise<void> => {
|
||||
it("it should throw a ValidationError if the inputs are invalid", async () => {
|
||||
await expect(service(...args)).rejects.toThrow(ValidationError);
|
||||
@@ -38,34 +48,34 @@ const testInputValidation = async (service: Function, ...args: any[]): Promise<v
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
prismaMock.person.findFirst.mockResolvedValue(mockPerson);
|
||||
prisma.person.findFirst.mockResolvedValue(mockPerson);
|
||||
});
|
||||
|
||||
describe("Tests for getDisplay", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns display associated with a given display ID", async () => {
|
||||
prismaMock.display.findUnique.mockResolvedValue(mockDisplay);
|
||||
prisma.display.findUnique.mockResolvedValue(mockDisplay);
|
||||
|
||||
const display = await getDisplay(mockDisplay.id);
|
||||
expect(display).toEqual(mockDisplay);
|
||||
});
|
||||
|
||||
it("Returns all displays associated with a given person ID", async () => {
|
||||
prismaMock.display.findMany.mockResolvedValue([mockDisplayWithPersonId]);
|
||||
prisma.display.findMany.mockResolvedValue([mockDisplayWithPersonId]);
|
||||
|
||||
const displays = await getDisplaysByPersonId(mockPerson.id);
|
||||
expect(displays).toEqual([mockDisplayWithPersonId]);
|
||||
});
|
||||
|
||||
it("Returns an empty array when no displays are found for the given person ID", async () => {
|
||||
prismaMock.display.findMany.mockResolvedValue([]);
|
||||
prisma.display.findMany.mockResolvedValue([]);
|
||||
|
||||
const displays = await getDisplaysByPersonId(mockPerson.id);
|
||||
expect(displays).toEqual([]);
|
||||
});
|
||||
|
||||
it("Returns display count for the given survey ID", async () => {
|
||||
prismaMock.display.count.mockResolvedValue(1);
|
||||
prisma.display.count.mockResolvedValue(1);
|
||||
|
||||
const displaCount = await getDisplayCountBySurveyId(mockSurveyId);
|
||||
expect(displaCount).toEqual(1);
|
||||
@@ -82,14 +92,14 @@ describe("Tests for getDisplay", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.display.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.display.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getDisplaysByPersonId(mockPerson.id)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.display.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.display.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getDisplaysByPersonId(mockPerson.id)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -99,14 +109,14 @@ describe("Tests for getDisplay", () => {
|
||||
describe("Tests for createDisplay service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Creates a new display when a userId exists", async () => {
|
||||
prismaMock.display.create.mockResolvedValue(mockDisplayWithPersonId);
|
||||
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId);
|
||||
|
||||
const display = await createDisplay(mockDisplayInputWithUserId);
|
||||
expect(display).toEqual(mockDisplayWithPersonId);
|
||||
});
|
||||
|
||||
it("Creates a new display when a userId does not exists", async () => {
|
||||
prismaMock.display.create.mockResolvedValue(mockDisplay);
|
||||
prisma.display.create.mockResolvedValue(mockDisplay);
|
||||
|
||||
const display = await createDisplay(mockDisplayInput);
|
||||
expect(display).toEqual(mockDisplay);
|
||||
@@ -123,14 +133,14 @@ describe("Tests for createDisplay service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.display.create.mockRejectedValue(errToThrow);
|
||||
prisma.display.create.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(createDisplay(mockDisplayInputWithUserId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.display.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.display.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(createDisplay(mockDisplayInput)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -140,7 +150,7 @@ describe("Tests for createDisplay service", () => {
|
||||
describe("Tests for updateDisplay Service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Updates a display (responded)", async () => {
|
||||
prismaMock.display.update.mockResolvedValue(mockDisplayWithResponseId);
|
||||
prisma.display.update.mockResolvedValue(mockDisplayWithResponseId);
|
||||
|
||||
const display = await updateDisplay(mockDisplay.id, mockDisplayUpdate);
|
||||
expect(display).toEqual(mockDisplayWithResponseId);
|
||||
@@ -157,14 +167,14 @@ describe("Tests for updateDisplay Service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.display.update.mockRejectedValue(errToThrow);
|
||||
prisma.display.update.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(updateDisplay(mockDisplay.id, mockDisplayUpdate)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other unexpected issues", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.display.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.display.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(updateDisplay(mockDisplay.id, mockDisplayUpdate)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -174,13 +184,13 @@ describe("Tests for updateDisplay Service", () => {
|
||||
describe("Tests for createDisplayLegacy service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Creates a display when a person ID exist", async () => {
|
||||
prismaMock.display.create.mockResolvedValue(mockDisplayWithPersonId);
|
||||
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId);
|
||||
|
||||
const display = await createDisplayLegacy(mockDisplayLegacyInputWithPersonId);
|
||||
expect(display).toEqual(mockDisplayWithPersonId);
|
||||
});
|
||||
it("Creates a display when a person ID does not exist", async () => {
|
||||
prismaMock.display.create.mockResolvedValue(mockDisplay);
|
||||
prisma.display.create.mockResolvedValue(mockDisplay);
|
||||
|
||||
const display = await createDisplayLegacy(mockDisplayLegacyInput);
|
||||
expect(display).toEqual(mockDisplay);
|
||||
@@ -196,14 +206,14 @@ describe("Tests for createDisplayLegacy service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.display.create.mockRejectedValue(errToThrow);
|
||||
prisma.display.create.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(createDisplayLegacy(mockDisplayLegacyInputWithPersonId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.display.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.display.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(createDisplayLegacy(mockDisplayLegacyInputWithPersonId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -213,14 +223,14 @@ describe("Tests for createDisplayLegacy service", () => {
|
||||
describe("Tests for updateDisplayLegacy Service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Updates a display", async () => {
|
||||
prismaMock.display.update.mockResolvedValue(mockDisplayWithPersonId);
|
||||
prisma.display.update.mockResolvedValue(mockDisplayWithPersonId);
|
||||
|
||||
const display = await updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput);
|
||||
expect(display).toEqual(mockDisplayWithPersonId);
|
||||
});
|
||||
|
||||
it("marks display as responded legacy", async () => {
|
||||
prismaMock.display.update.mockResolvedValue(mockDisplayLegacyWithRespondedStatus);
|
||||
prisma.display.update.mockResolvedValue(mockDisplayLegacyWithRespondedStatus);
|
||||
|
||||
const display = await markDisplayRespondedLegacy(mockDisplay.id);
|
||||
expect(display).toEqual(mockDisplayLegacyWithRespondedStatus);
|
||||
@@ -237,7 +247,7 @@ describe("Tests for updateDisplayLegacy Service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.display.update.mockRejectedValue(errToThrow);
|
||||
prisma.display.update.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput)).rejects.toThrow(
|
||||
DatabaseError
|
||||
@@ -246,7 +256,7 @@ describe("Tests for updateDisplayLegacy Service", () => {
|
||||
|
||||
it("Throws a generic Error for other unexpected issues", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.display.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.display.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -256,7 +266,7 @@ describe("Tests for updateDisplayLegacy Service", () => {
|
||||
describe("Tests for deleteDisplayByResponseId service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Deletes a display when a response associated to it is deleted", async () => {
|
||||
prismaMock.display.delete.mockResolvedValue(mockDisplayWithResponseId);
|
||||
prisma.display.delete.mockResolvedValue(mockDisplayWithResponseId);
|
||||
|
||||
const display = await deleteDisplayByResponseId(mockResponseId, mockSurveyId);
|
||||
expect(display).toEqual(mockDisplayWithResponseId);
|
||||
@@ -272,14 +282,14 @@ describe("Tests for deleteDisplayByResponseId service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.display.delete.mockRejectedValue(errToThrow);
|
||||
prisma.display.delete.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(deleteDisplayByResponseId(mockResponseId, mockSurveyId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.display.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.display.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(deleteDisplayByResponseId(mockResponseId, mockSurveyId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -7,42 +7,31 @@ export const env = createEnv({
|
||||
* Will throw if you access these variables on the client.
|
||||
*/
|
||||
server: {
|
||||
AIRTABLE_CLIENT_ID: z.string().optional(),
|
||||
AWS_ACCESS_KEY: z.string().optional(),
|
||||
AWS_SECRET_KEY: z.string().optional(),
|
||||
AZUREAD_CLIENT_ID: z.string().optional(),
|
||||
AZUREAD_CLIENT_SECRET: z.string().optional(),
|
||||
AZUREAD_TENANT_ID: z.string().optional(),
|
||||
CRON_SECRET: z.string().optional(),
|
||||
CUSTOMER_IO_API_KEY: z.string().optional(),
|
||||
CUSTOMER_IO_SITE_ID: z.string().optional(),
|
||||
NEXT_PUBLIC_POSTHOG_API_HOST: z.string().optional(),
|
||||
WEBAPP_URL: z.string().url().optional(),
|
||||
DATABASE_URL: z.string().url(),
|
||||
DEBUG: z.enum(["1", "0"]).optional(),
|
||||
DEFAULT_TEAM_ID: z.string().optional(),
|
||||
DEFAULT_TEAM_ROLE: z.enum(["owner", "admin", "editor", "developer", "viewer"]).optional(),
|
||||
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
ENCRYPTION_KEY: z.string().length(64).or(z.string().length(32)),
|
||||
ENTERPRISE_LICENSE_KEY: z.string().optional(),
|
||||
FORMBRICKS_ENCRYPTION_KEY: z.string().length(24).or(z.string().length(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(),
|
||||
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
PRIVACY_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.or(z.string().refine((str) => str === "")),
|
||||
TERMS_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.or(z.string().refine((str) => str === "")),
|
||||
GOOGLE_SHEETS_CLIENT_ID: z.string().optional(),
|
||||
GOOGLE_SHEETS_CLIENT_SECRET: z.string().optional(),
|
||||
GOOGLE_SHEETS_REDIRECT_URL: z.string().optional(),
|
||||
IMPRINT_URL: z
|
||||
.string()
|
||||
.url()
|
||||
@@ -50,33 +39,45 @@ export const env = createEnv({
|
||||
.or(z.string().refine((str) => str === "")),
|
||||
INVITE_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
IS_FORMBRICKS_CLOUD: z.enum(["1", "0"]).optional(),
|
||||
VERCEL_URL: z.string().optional(),
|
||||
SHORT_URL_BASE: z.string().url().optional().or(z.string().length(0)),
|
||||
GOOGLE_SHEETS_CLIENT_ID: z.string().optional(),
|
||||
GOOGLE_SHEETS_CLIENT_SECRET: z.string().optional(),
|
||||
GOOGLE_SHEETS_REDIRECT_URL: z.string().optional(),
|
||||
AIRTABLE_CLIENT_ID: z.string().optional(),
|
||||
AWS_ACCESS_KEY: z.string().optional(),
|
||||
AWS_SECRET_KEY: z.string().optional(),
|
||||
S3_ACCESS_KEY: z.string().optional(),
|
||||
S3_SECRET_KEY: z.string().optional(),
|
||||
S3_REGION: z.string().optional(),
|
||||
S3_BUCKET_NAME: z.string().optional(),
|
||||
MAIL_FROM: z.string().email().optional(),
|
||||
NEXTAUTH_SECRET: z.string().min(1),
|
||||
NEXTAUTH_URL: z.string().url().optional(),
|
||||
NOTION_OAUTH_CLIENT_ID: z.string().optional(),
|
||||
NOTION_OAUTH_CLIENT_SECRET: z.string().optional(),
|
||||
AZUREAD_CLIENT_SECRET: z.string().optional(),
|
||||
AZUREAD_TENANT_ID: z.string().optional(),
|
||||
AZUREAD_CLIENT_ID: z.string().optional(),
|
||||
DEFAULT_TEAM_ID: z.string().optional(),
|
||||
DEFAULT_TEAM_ROLE: z.enum(["owner", "admin", "editor", "developer", "viewer"]).optional(),
|
||||
ONBOARDING_DISABLED: z.string().optional(),
|
||||
ENTERPRISE_LICENSE_KEY: z.string().optional(),
|
||||
RATE_LIMITING_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
OIDC_DISPLAY_NAME: z.string().optional(),
|
||||
OIDC_CLIENT_ID: z.string().optional(),
|
||||
OIDC_CLIENT_SECRET: z.string().optional(),
|
||||
OIDC_DISPLAY_NAME: z.string().optional(),
|
||||
OIDC_ISSUER: z.string().optional(),
|
||||
OIDC_SIGNING_ALGORITHM: z.string().optional(),
|
||||
ONBOARDING_DISABLED: z.string().optional(),
|
||||
PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
PRIVACY_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.or(z.string().refine((str) => str === "")),
|
||||
RATE_LIMITING_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
S3_ACCESS_KEY: z.string().optional(),
|
||||
S3_BUCKET_NAME: z.string().optional(),
|
||||
S3_REGION: z.string().optional(),
|
||||
S3_SECRET_KEY: z.string().optional(),
|
||||
SHORT_URL_BASE: z.string().url().optional().or(z.string().length(0)),
|
||||
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
SMTP_HOST: z.string().min(1).optional(),
|
||||
SMTP_PASSWORD: z.string().min(1).optional(),
|
||||
SMTP_PORT: z.string().min(1).optional(),
|
||||
SMTP_SECURE_ENABLED: z.enum(["1", "0"]).optional(),
|
||||
SMTP_USER: z.string().min(1).optional(),
|
||||
STRIPE_SECRET_KEY: z.string().optional(),
|
||||
STRIPE_WEBHOOK_SECRET: z.string().optional(),
|
||||
TELEMETRY_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
TERMS_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.or(z.string().refine((str) => str === "")),
|
||||
VERCEL_URL: z.string().optional(),
|
||||
WEBAPP_URL: z.string().url().optional(),
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -103,66 +104,70 @@ export const env = createEnv({
|
||||
* 💡 You'll get type errors if not all variables from `server` & `client` are included here.
|
||||
*/
|
||||
runtimeEnv: {
|
||||
AIRTABLE_CLIENT_ID: process.env.AIRTABLE_CLIENT_ID,
|
||||
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
|
||||
AWS_SECRET_KEY: process.env.AWS_SECRET_KEY,
|
||||
AZUREAD_CLIENT_ID: process.env.AZUREAD_CLIENT_ID,
|
||||
AZUREAD_CLIENT_SECRET: process.env.AZUREAD_CLIENT_SECRET,
|
||||
AZUREAD_TENANT_ID: process.env.AZUREAD_TENANT_ID,
|
||||
CRON_SECRET: process.env.CRON_SECRET,
|
||||
CUSTOMER_IO_API_KEY: process.env.CUSTOMER_IO_API_KEY,
|
||||
CUSTOMER_IO_SITE_ID: process.env.CUSTOMER_IO_SITE_ID,
|
||||
WEBAPP_URL: process.env.WEBAPP_URL,
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
DEBUG: process.env.DEBUG,
|
||||
DEFAULT_TEAM_ID: process.env.DEFAULT_TEAM_ID,
|
||||
DEFAULT_TEAM_ROLE: process.env.DEFAULT_TEAM_ROLE,
|
||||
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
|
||||
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
|
||||
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
||||
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,
|
||||
ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY,
|
||||
FORMBRICKS_ENCRYPTION_KEY: process.env.FORMBRICKS_ENCRYPTION_KEY,
|
||||
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,
|
||||
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
|
||||
PASSWORD_RESET_DISABLED: process.env.PASSWORD_RESET_DISABLED,
|
||||
SIGNUP_DISABLED: process.env.SIGNUP_DISABLED,
|
||||
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
|
||||
INVITE_DISABLED: process.env.INVITE_DISABLED,
|
||||
PRIVACY_URL: process.env.PRIVACY_URL,
|
||||
TERMS_URL: process.env.TERMS_URL,
|
||||
IMPRINT_URL: process.env.IMPRINT_URL,
|
||||
GOOGLE_SHEETS_CLIENT_ID: process.env.GOOGLE_SHEETS_CLIENT_ID,
|
||||
GOOGLE_SHEETS_CLIENT_SECRET: process.env.GOOGLE_SHEETS_CLIENT_SECRET,
|
||||
GOOGLE_SHEETS_REDIRECT_URL: process.env.GOOGLE_SHEETS_REDIRECT_URL,
|
||||
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
||||
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
|
||||
S3_REGION: process.env.S3_REGION,
|
||||
S3_BUCKET_NAME: process.env.S3_BUCKET_NAME,
|
||||
NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID,
|
||||
NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET,
|
||||
IMPRINT_URL: process.env.IMPRINT_URL,
|
||||
INVITE_DISABLED: process.env.INVITE_DISABLED,
|
||||
IS_FORMBRICKS_CLOUD: process.env.IS_FORMBRICKS_CLOUD,
|
||||
MAIL_FROM: process.env.MAIL_FROM,
|
||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||
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,
|
||||
IS_FORMBRICKS_CLOUD: process.env.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,
|
||||
FORMBRICKS_ENCRYPTION_KEY: process.env.FORMBRICKS_ENCRYPTION_KEY,
|
||||
VERCEL_URL: process.env.VERCEL_URL,
|
||||
SHORT_URL_BASE: process.env.SHORT_URL_BASE,
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
AZUREAD_CLIENT_ID: process.env.AZUREAD_CLIENT_ID,
|
||||
AZUREAD_CLIENT_SECRET: process.env.AZUREAD_CLIENT_SECRET,
|
||||
AZUREAD_TENANT_ID: process.env.AZUREAD_TENANT_ID,
|
||||
AIRTABLE_CLIENT_ID: process.env.AIRTABLE_CLIENT_ID,
|
||||
DEFAULT_TEAM_ID: process.env.DEFAULT_TEAM_ID,
|
||||
DEFAULT_TEAM_ROLE: process.env.DEFAULT_TEAM_ROLE,
|
||||
ONBOARDING_DISABLED: process.env.ONBOARDING_DISABLED,
|
||||
ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY,
|
||||
RATE_LIMITING_DISABLED: process.env.RATE_LIMITING_DISABLED,
|
||||
OIDC_DISPLAY_NAME: process.env.OIDC_DISPLAY_NAME,
|
||||
NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID,
|
||||
NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET,
|
||||
OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID,
|
||||
OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET,
|
||||
OIDC_DISPLAY_NAME: process.env.OIDC_DISPLAY_NAME,
|
||||
OIDC_ISSUER: process.env.OIDC_ISSUER,
|
||||
OIDC_SIGNING_ALGORITHM: process.env.OIDC_SIGNING_ALGORITHM,
|
||||
ONBOARDING_DISABLED: process.env.ONBOARDING_DISABLED,
|
||||
PASSWORD_RESET_DISABLED: process.env.PASSWORD_RESET_DISABLED,
|
||||
PRIVACY_URL: process.env.PRIVACY_URL,
|
||||
RATE_LIMITING_DISABLED: process.env.RATE_LIMITING_DISABLED,
|
||||
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
||||
S3_BUCKET_NAME: process.env.S3_BUCKET_NAME,
|
||||
S3_REGION: process.env.S3_REGION,
|
||||
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
|
||||
SHORT_URL_BASE: process.env.SHORT_URL_BASE,
|
||||
SIGNUP_DISABLED: process.env.SIGNUP_DISABLED,
|
||||
SMTP_HOST: process.env.SMTP_HOST,
|
||||
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
|
||||
SMTP_PORT: process.env.SMTP_PORT,
|
||||
SMTP_SECURE_ENABLED: process.env.SMTP_SECURE_ENABLED,
|
||||
SMTP_USER: process.env.SMTP_USER,
|
||||
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
|
||||
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
|
||||
TELEMETRY_DISABLED: process.env.TELEMETRY_DISABLED,
|
||||
TERMS_URL: process.env.TERMS_URL,
|
||||
VERCEL_URL: process.env.VERCEL_URL,
|
||||
WEBAPP_URL: process.env.WEBAPP_URL,
|
||||
},
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Config } from "jest";
|
||||
|
||||
const config: Config = {
|
||||
preset: "ts-jest",
|
||||
collectCoverage: true,
|
||||
// on node 14.x coverage provider v8 offers good speed and more or less good report
|
||||
coverageProvider: "v8",
|
||||
testMatch: ["**/*.unit.ts"],
|
||||
collectCoverageFrom: [
|
||||
"**/*.{js,jsx,ts,tsx}",
|
||||
"!**/*.d.ts",
|
||||
"!**/node_modules/**",
|
||||
"!<rootDir>/out/**",
|
||||
"!<rootDir>/.next/**",
|
||||
"!<rootDir>/*.config.js",
|
||||
"!<rootDir>/*.config.ts",
|
||||
"!<rootDir>/coverage/**",
|
||||
"!<rootDir>/jest/**",
|
||||
],
|
||||
testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/.next/"],
|
||||
setupFilesAfterEnv: ["<rootDir>/jest/jestSetup.ts", "<rootDir>/../database/src/jestClient.ts"],
|
||||
transform: {
|
||||
// Use babel-jest to transpile tests with the next/babel preset
|
||||
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
|
||||
"^.+\\.(ts|tsx)$": "ts-jest",
|
||||
"^.+\\.(js|jsx)$": ["babel-jest", { presets: ["next/babel"] }],
|
||||
"^.+\\.mjs$": "babel-jest",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/",
|
||||
"^.+\\.module\\.(css|sass|scss)$",
|
||||
"node_modules/(?!uuid).+\\.js$",
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"^uuid$": "uuid",
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -2,7 +2,7 @@ import jwt, { JwtPayload } from "jsonwebtoken";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
|
||||
import { env } from "./env.mjs";
|
||||
import { env } from "./env";
|
||||
|
||||
export function createToken(userId: string, userEmail: string, options = {}): string {
|
||||
return jwt.sign({ id: userId }, env.NEXTAUTH_SECRET + userEmail, options);
|
||||
|
||||
@@ -10,27 +10,27 @@
|
||||
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
|
||||
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
|
||||
"lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json",
|
||||
"test:dev": "jest --coverage --watch",
|
||||
"test": "jest -ci --coverage --no-cache --silent"
|
||||
"test:dev": "vitest",
|
||||
"test": "dotenv -e ../../.env -- vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/s3-presigned-post": "3.499.0",
|
||||
"@aws-sdk/client-s3": "3.499.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.499.0",
|
||||
"@t3-oss/env-nextjs": "^0.8.0",
|
||||
"@aws-sdk/client-s3": "3.521.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.521.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.521.0",
|
||||
"@formbricks/api": "*",
|
||||
"@formbricks/database": "*",
|
||||
"@formbricks/types": "*",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"aws-crt": "^1.21.0",
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"aws-crt": "^1.21.1",
|
||||
"date-fns": "^3.3.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"markdown-it": "^14.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"nanoid": "^5.0.4",
|
||||
"next-auth": "^4.24.5",
|
||||
"nodemailer": "^6.9.8",
|
||||
"posthog-node": "^3.6.0",
|
||||
"nanoid": "^5.0.6",
|
||||
"next-auth": "^4.24.6",
|
||||
"nodemailer": "^6.9.10",
|
||||
"posthog-node": "^3.6.3",
|
||||
"server-only": "^0.0.1",
|
||||
"tailwind-merge": "^2.2.1"
|
||||
},
|
||||
@@ -38,11 +38,10 @@
|
||||
"@formbricks/tsconfig": "*",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"babel-jest": "^29.7.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"jest": "^29.7.0",
|
||||
"jest-mock-extended": "^3.0.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-node": "^10.9.2"
|
||||
"ts-node": "^10.9.2",
|
||||
"vitest": "^1.3.1",
|
||||
"vitest-mock-extended": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { PostHog } from "posthog-node";
|
||||
|
||||
import { env } from "./env";
|
||||
|
||||
const enabled =
|
||||
process.env.NODE_ENV === "production" &&
|
||||
process.env.NEXT_PUBLIC_POSTHOG_API_HOST &&
|
||||
process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
|
||||
env.NEXT_PUBLIC_POSTHOG_API_HOST &&
|
||||
env.NEXT_PUBLIC_POSTHOG_API_KEY;
|
||||
|
||||
export const capturePosthogEnvironmentEvent = async (
|
||||
environmentId: string,
|
||||
@@ -12,14 +14,14 @@ export const capturePosthogEnvironmentEvent = async (
|
||||
) => {
|
||||
if (
|
||||
!enabled ||
|
||||
typeof process.env.NEXT_PUBLIC_POSTHOG_API_HOST !== "string" ||
|
||||
typeof process.env.NEXT_PUBLIC_POSTHOG_API_KEY !== "string"
|
||||
typeof env.NEXT_PUBLIC_POSTHOG_API_HOST !== "string" ||
|
||||
typeof env.NEXT_PUBLIC_POSTHOG_API_KEY !== "string"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, {
|
||||
host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
|
||||
const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_API_KEY, {
|
||||
host: env.NEXT_PUBLIC_POSTHOG_API_HOST,
|
||||
});
|
||||
client.capture({
|
||||
distinctId: environmentId,
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
|
||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL, WEBAPP_URL } from "../constants";
|
||||
import { deleteDisplayByResponseId } from "../display/service";
|
||||
import { createPerson, getPerson, getPersonByUserId, transformPrismaPerson } from "../person/service";
|
||||
import {
|
||||
@@ -576,7 +576,7 @@ export const getResponseDownloadUrl = async (
|
||||
|
||||
await putFile(fileName, fileBuffer, accessType, environmentId);
|
||||
|
||||
return `${process.env.WEBAPP_URL}/storage/${environmentId}/${accessType}/${fileName}`;
|
||||
return `${WEBAPP_URL}/storage/${environmentId}/${accessType}/${fileName}`;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { prisma } from "../../__mocks__/database";
|
||||
import {
|
||||
getFilteredMockResponses,
|
||||
getMockUpdateResponseInput,
|
||||
@@ -19,8 +20,8 @@ import {
|
||||
} from "./__mocks__/data.mock";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { prismaMock } from "@formbricks/database/src/jestClient";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
||||
import {
|
||||
TResponse,
|
||||
@@ -31,7 +32,7 @@ import {
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
|
||||
import { selectPerson, transformPrismaPerson } from "../../person/service";
|
||||
import { mockSurveyOutput } from "../../survey/tests/survey.mock";
|
||||
import { mockSurveyOutput } from "../../survey/tests/__mock__/survey.mock";
|
||||
import {
|
||||
createResponse,
|
||||
createResponseLegacy,
|
||||
@@ -87,7 +88,7 @@ const createMockResponseLegacyInput = (personId?: string): TResponseLegacyInput
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error
|
||||
prismaMock.response.create.mockImplementation(async (args) => {
|
||||
prisma.response.create.mockImplementation(async (args) => {
|
||||
if (args.data.person && args.data.person.connect) {
|
||||
return {
|
||||
...mockResponse,
|
||||
@@ -99,13 +100,13 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
// mocking the person findFirst call as it is used in the transformPrismaPerson function
|
||||
prismaMock.person.findFirst.mockResolvedValue(mockPerson);
|
||||
prismaMock.responseNote.findMany.mockResolvedValue([mockResponseNote]);
|
||||
prisma.person.findFirst.mockResolvedValue(mockPerson);
|
||||
prisma.responseNote.findMany.mockResolvedValue([mockResponseNote]);
|
||||
|
||||
prismaMock.response.findUnique.mockResolvedValue(mockResponse);
|
||||
prisma.response.findUnique.mockResolvedValue(mockResponse);
|
||||
|
||||
// @ts-expect-error
|
||||
prismaMock.response.update.mockImplementation(async (args) => {
|
||||
prisma.response.update.mockImplementation(async (args) => {
|
||||
if (args.data.finished === true) {
|
||||
return {
|
||||
...mockResponse,
|
||||
@@ -121,12 +122,12 @@ beforeEach(() => {
|
||||
};
|
||||
});
|
||||
|
||||
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
|
||||
prismaMock.response.delete.mockResolvedValue(mockResponse);
|
||||
prisma.response.findMany.mockResolvedValue([mockResponse]);
|
||||
prisma.response.delete.mockResolvedValue(mockResponse);
|
||||
|
||||
prismaMock.display.delete.mockResolvedValue({ ...mockDisplay, status: "seen" });
|
||||
prisma.display.delete.mockResolvedValue({ ...mockDisplay, status: "seen" });
|
||||
|
||||
prismaMock.response.count.mockResolvedValue(1);
|
||||
prisma.response.count.mockResolvedValue(1);
|
||||
});
|
||||
|
||||
// utility function to test input validation for all services
|
||||
@@ -139,14 +140,14 @@ const testInputValidation = async (service: Function, ...args: any[]): Promise<v
|
||||
describe("Tests for getResponsesByPersonId", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns all responses associated with a given person ID", async () => {
|
||||
prismaMock.response.findMany.mockResolvedValue([mockResponseWithMockPerson]);
|
||||
prisma.response.findMany.mockResolvedValue([mockResponseWithMockPerson]);
|
||||
|
||||
const responses = await getResponsesByPersonId(mockPerson.id);
|
||||
expect(responses).toEqual([expectedResponseWithPerson]);
|
||||
});
|
||||
|
||||
it("Returns an empty array when no responses are found for the given person ID", async () => {
|
||||
prismaMock.response.findMany.mockResolvedValue([]);
|
||||
prisma.response.findMany.mockResolvedValue([]);
|
||||
|
||||
const responses = await getResponsesByPersonId(mockPerson.id);
|
||||
expect(responses).toEqual([]);
|
||||
@@ -163,14 +164,14 @@ describe("Tests for getResponsesByPersonId", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.response.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponsesByPersonId(mockPerson.id)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponsesByPersonId(mockPerson.id)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -195,14 +196,14 @@ describe("Tests for getResponsesBySingleUseId", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.findUnique.mockRejectedValue(errToThrow);
|
||||
prisma.response.findUnique.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponseBySingleUseId(mockSurveyId, mockSingleUseId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponseBySingleUseId(mockSurveyId, mockSingleUseId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -222,13 +223,13 @@ describe("Tests for createResponse service", () => {
|
||||
});
|
||||
|
||||
it("Creates a new person and response when the person does not exist", async () => {
|
||||
prismaMock.person.findFirst.mockResolvedValue(null);
|
||||
prismaMock.person.create.mockResolvedValue(mockPerson);
|
||||
prisma.person.findFirst.mockResolvedValue(null);
|
||||
prisma.person.create.mockResolvedValue(mockPerson);
|
||||
const response = await createResponse(mockResponseInputWithUserId);
|
||||
|
||||
expect(response).toEqual(expectedResponseWithPerson);
|
||||
|
||||
expect(prismaMock.person.create).toHaveBeenCalledWith({
|
||||
expect(prisma.person.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
environment: { connect: { id: mockEnvironmentId } },
|
||||
userId: mockUserId,
|
||||
@@ -251,14 +252,14 @@ describe("Tests for createResponse service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.create.mockRejectedValue(errToThrow);
|
||||
prisma.response.create.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(createResponse(mockResponseInputWithUserId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(createResponse(mockResponseInputWithUserId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -291,7 +292,7 @@ describe("Tests for getResponse service", () => {
|
||||
testInputValidation(getResponse, "123");
|
||||
|
||||
it("Throws ResourceNotFoundError if no response is found", async () => {
|
||||
prismaMock.response.findUnique.mockResolvedValue(null);
|
||||
prisma.response.findUnique.mockResolvedValue(null);
|
||||
const response = await getResponse(mockResponse.id);
|
||||
expect(response).toBeNull();
|
||||
});
|
||||
@@ -303,14 +304,14 @@ describe("Tests for getResponse service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.findUnique.mockRejectedValue(errToThrow);
|
||||
prisma.response.findUnique.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponse(mockResponse.id)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other unexpected issues", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponse(mockResponse.id)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -320,13 +321,13 @@ describe("Tests for getResponse service", () => {
|
||||
describe("Tests for getAttributesFromResponses service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Retrieves all attributes from responses for a given survey ID", async () => {
|
||||
prismaMock.response.findMany.mockResolvedValue(mockResponsePersonAttributes);
|
||||
prisma.response.findMany.mockResolvedValue(mockResponsePersonAttributes);
|
||||
const attributes = await getResponsePersonAttributes(mockSurveyId);
|
||||
expect(attributes).toEqual(mockPersonAttributesData);
|
||||
});
|
||||
|
||||
it("Returns an empty Object when no responses with attributes are found for the given survey ID", async () => {
|
||||
prismaMock.response.findMany.mockResolvedValue([]);
|
||||
prisma.response.findMany.mockResolvedValue([]);
|
||||
|
||||
const responses = await getResponsePersonAttributes(mockSurveyId);
|
||||
expect(responses).toEqual({});
|
||||
@@ -343,14 +344,14 @@ describe("Tests for getAttributesFromResponses service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.response.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponsePersonAttributes(mockSurveyId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected problems", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponsePersonAttributes(mockSurveyId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -372,7 +373,7 @@ describe("Tests for getResponses service", () => {
|
||||
let expectedWhereClause: Prisma.ResponseWhereInput | undefined = {};
|
||||
|
||||
// @ts-expect-error
|
||||
prismaMock.response.findMany.mockImplementation(async (args) => {
|
||||
prisma.response.findMany.mockImplementation(async (args) => {
|
||||
expectedWhereClause = args?.where;
|
||||
return getFilteredMockResponses({ finished: true }, false);
|
||||
});
|
||||
@@ -411,7 +412,7 @@ describe("Tests for getResponses service", () => {
|
||||
let expectedWhereClause: Prisma.ResponseWhereInput | undefined = {};
|
||||
|
||||
// @ts-expect-error
|
||||
prismaMock.response.findMany.mockImplementation(async (args) => {
|
||||
prisma.response.findMany.mockImplementation(async (args) => {
|
||||
expectedWhereClause = args?.where;
|
||||
return getFilteredMockResponses(criteria, false);
|
||||
});
|
||||
@@ -429,7 +430,7 @@ describe("Tests for getResponses service", () => {
|
||||
let expectedWhereClause: Prisma.ResponseWhereInput | undefined = {};
|
||||
|
||||
// @ts-expect-error
|
||||
prismaMock.response.findMany.mockImplementation(async (args) => {
|
||||
prisma.response.findMany.mockImplementation(async (args) => {
|
||||
expectedWhereClause = args?.where;
|
||||
|
||||
return getFilteredMockResponses({ finished: true });
|
||||
@@ -453,14 +454,14 @@ describe("Tests for getResponses service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.response.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponses(mockSurveyId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected problems", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponses(mockSurveyId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -470,9 +471,9 @@ describe("Tests for getResponses service", () => {
|
||||
describe("Tests for getResponseDownloadUrl service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns a download URL for the csv response file", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prismaMock.response.count.mockResolvedValue(1);
|
||||
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
|
||||
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prisma.response.count.mockResolvedValue(1);
|
||||
prisma.response.findMany.mockResolvedValue([mockResponse]);
|
||||
|
||||
const url = await getResponseDownloadUrl(mockSurveyId, "csv");
|
||||
const fileExtension = url.split(".").pop();
|
||||
@@ -480,9 +481,9 @@ describe("Tests for getResponseDownloadUrl service", () => {
|
||||
});
|
||||
|
||||
it("Returns a download URL for the xlsx response file", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prismaMock.response.count.mockResolvedValue(1);
|
||||
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
|
||||
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prisma.response.count.mockResolvedValue(1);
|
||||
prisma.response.findMany.mockResolvedValue([mockResponse]);
|
||||
|
||||
const url = await getResponseDownloadUrl(mockSurveyId, "xlsx", { finished: true });
|
||||
const fileExtension = url.split(".").pop();
|
||||
@@ -494,9 +495,9 @@ describe("Tests for getResponseDownloadUrl service", () => {
|
||||
testInputValidation(getResponseDownloadUrl, mockSurveyId, 123);
|
||||
|
||||
it("Throws error if response file is of different format than expected", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prismaMock.response.count.mockResolvedValue(1);
|
||||
prismaMock.response.findMany.mockResolvedValue([mockResponse]);
|
||||
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prisma.response.count.mockResolvedValue(1);
|
||||
prisma.response.findMany.mockResolvedValue([mockResponse]);
|
||||
|
||||
const url = await getResponseDownloadUrl(mockSurveyId, "csv", { finished: true });
|
||||
const fileExtension = url.split(".").pop();
|
||||
@@ -509,8 +510,8 @@ describe("Tests for getResponseDownloadUrl service", () => {
|
||||
code: "P2002",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prismaMock.response.count.mockRejectedValue(errToThrow);
|
||||
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prisma.response.count.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponseDownloadUrl(mockSurveyId, "csv")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
@@ -522,9 +523,9 @@ describe("Tests for getResponseDownloadUrl service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prismaMock.response.count.mockResolvedValue(1);
|
||||
prismaMock.response.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.survey.findUnique.mockResolvedValue(mockSurveyOutput);
|
||||
prisma.response.count.mockResolvedValue(1);
|
||||
prisma.response.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponseDownloadUrl(mockSurveyId, "csv")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
@@ -533,7 +534,7 @@ describe("Tests for getResponseDownloadUrl service", () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
|
||||
// error from getSurvey
|
||||
prismaMock.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponseDownloadUrl(mockSurveyId, "xlsx")).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -558,14 +559,14 @@ describe("Tests for getResponsesByEnvironmentId", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.response.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getResponsesByEnvironmentId(mockEnvironmentId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for any other unhandled exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponsesByEnvironmentId(mockEnvironmentId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -596,7 +597,7 @@ describe("Tests for updateResponse Service", () => {
|
||||
testInputValidation(updateResponse, "123", {});
|
||||
|
||||
it("Throws ResourceNotFoundError if no response is found", async () => {
|
||||
prismaMock.response.findUnique.mockResolvedValue(null);
|
||||
prisma.response.findUnique.mockResolvedValue(null);
|
||||
await expect(updateResponse(mockResponse.id, getMockUpdateResponseInput())).rejects.toThrow(
|
||||
ResourceNotFoundError
|
||||
);
|
||||
@@ -609,7 +610,7 @@ describe("Tests for updateResponse Service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.update.mockRejectedValue(errToThrow);
|
||||
prisma.response.update.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(updateResponse(mockResponse.id, getMockUpdateResponseInput())).rejects.toThrow(
|
||||
DatabaseError
|
||||
@@ -618,7 +619,7 @@ describe("Tests for updateResponse Service", () => {
|
||||
|
||||
it("Throws a generic Error for other unexpected issues", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(updateResponse(mockResponse.id, getMockUpdateResponseInput())).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -643,14 +644,14 @@ describe("Tests for deleteResponse service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.response.delete.mockRejectedValue(errToThrow);
|
||||
prisma.response.delete.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(deleteResponse(mockResponse.id)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for any unhandled exception during deletion", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(deleteResponse(mockResponse.id)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -665,7 +666,7 @@ describe("Tests for getResponseCountBySurveyId service", () => {
|
||||
});
|
||||
|
||||
it("Returns zero count when there are no responses for a given survey ID", async () => {
|
||||
prismaMock.response.count.mockResolvedValue(0);
|
||||
prisma.response.count.mockResolvedValue(0);
|
||||
const count = await getResponseCountBySurveyId(mockSurveyId);
|
||||
expect(count).toEqual(0);
|
||||
});
|
||||
@@ -676,7 +677,7 @@ describe("Tests for getResponseCountBySurveyId service", () => {
|
||||
|
||||
it("Throws a generic Error for other unexpected issues", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.response.count.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.response.count.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getResponseCountBySurveyId(mockSurveyId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { prisma } from "../../__mocks__/database";
|
||||
import {
|
||||
getMockSegmentFilters,
|
||||
mockEnvironmentId,
|
||||
@@ -11,11 +12,11 @@ import {
|
||||
} from "./__mocks__/segment.mock";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { prismaMock } from "@formbricks/database/src/jestClient";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
|
||||
import { testInputValidation } from "../../jest/jestSetup";
|
||||
import { testInputValidation } from "../../vitestSetup";
|
||||
import {
|
||||
cloneSegment,
|
||||
createSegment,
|
||||
@@ -31,9 +32,9 @@ function addOrSubractDays(date: Date, number: number) {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
prismaMock.segment.findUnique.mockResolvedValue(mockSegmentPrisma);
|
||||
prismaMock.segment.findMany.mockResolvedValue([mockSegmentPrisma]);
|
||||
prismaMock.segment.update.mockResolvedValue({
|
||||
prisma.segment.findUnique.mockResolvedValue(mockSegmentPrisma);
|
||||
prisma.segment.findMany.mockResolvedValue([mockSegmentPrisma]);
|
||||
prisma.segment.update.mockResolvedValue({
|
||||
...mockSegmentPrisma,
|
||||
filters: getMockSegmentFilters("lastMonthCount", 5, "greaterEqual"),
|
||||
});
|
||||
@@ -42,7 +43,7 @@ beforeEach(() => {
|
||||
describe("Tests for evaluateSegment service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns true when the user meets the segment criteria", async () => {
|
||||
prismaMock.action.count.mockResolvedValue(4);
|
||||
prisma.action.count.mockResolvedValue(4);
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
getMockSegmentFilters("lastQuarterCount", 5, "lessThan")
|
||||
@@ -51,7 +52,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
});
|
||||
|
||||
it("Calculates the action count for the last month", async () => {
|
||||
prismaMock.action.count.mockResolvedValue(0);
|
||||
prisma.action.count.mockResolvedValue(0);
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
getMockSegmentFilters("lastMonthCount", 5, "lessThan")
|
||||
@@ -60,7 +61,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
});
|
||||
|
||||
it("Calculates the action count for the last week", async () => {
|
||||
prismaMock.action.count.mockResolvedValue(6);
|
||||
prisma.action.count.mockResolvedValue(6);
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
getMockSegmentFilters("lastWeekCount", 5, "greaterEqual")
|
||||
@@ -69,7 +70,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
});
|
||||
|
||||
it("Calculates the total occurences of action", async () => {
|
||||
prismaMock.action.count.mockResolvedValue(6);
|
||||
prisma.action.count.mockResolvedValue(6);
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
getMockSegmentFilters("occuranceCount", 5, "greaterEqual")
|
||||
@@ -78,7 +79,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
});
|
||||
|
||||
it("Calculates the last occurence days ago of action", async () => {
|
||||
prismaMock.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
|
||||
prisma.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
|
||||
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
@@ -88,7 +89,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
});
|
||||
|
||||
it("Calculates the first occurence days ago of action", async () => {
|
||||
prismaMock.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
|
||||
prisma.action.findFirst.mockResolvedValue({ createdAt: addOrSubractDays(new Date(), 5) } as any);
|
||||
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
@@ -100,7 +101,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
|
||||
describe("Sad Path", () => {
|
||||
it("Returns false when the user does not meet the segment criteria", async () => {
|
||||
prismaMock.action.count.mockResolvedValue(0);
|
||||
prisma.action.count.mockResolvedValue(0);
|
||||
const result = await evaluateSegment(
|
||||
mockEvaluateSegmentUserData,
|
||||
getMockSegmentFilters("lastQuarterCount", 5, "greaterThan")
|
||||
@@ -113,7 +114,7 @@ describe("Tests for evaluateSegment service", () => {
|
||||
describe("Tests for createSegment service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Creates a new user segment", async () => {
|
||||
prismaMock.segment.create.mockResolvedValue(mockSegmentPrisma);
|
||||
prisma.segment.create.mockResolvedValue(mockSegmentPrisma);
|
||||
const result = await createSegment(mockSegmentCreateInput);
|
||||
expect(result).toEqual(mockSegment);
|
||||
});
|
||||
@@ -129,14 +130,14 @@ describe("Tests for createSegment service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.segment.create.mockRejectedValue(errToThrow);
|
||||
prisma.segment.create.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(createSegment(mockSegmentCreateInput)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.segment.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.segment.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(createSegment(mockSegmentCreateInput)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -161,14 +162,14 @@ describe("Tests for getSegments service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.segment.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.segment.findMany.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getSegments(mockEnvironmentId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.segment.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.segment.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getSegments(mockEnvironmentId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -187,7 +188,7 @@ describe("Tests for getSegment service", () => {
|
||||
testInputValidation(getSegment, "123");
|
||||
|
||||
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
|
||||
prismaMock.segment.findUnique.mockResolvedValue(null);
|
||||
prisma.segment.findUnique.mockResolvedValue(null);
|
||||
await expect(getSegment(mockSegmentId)).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
|
||||
@@ -198,14 +199,14 @@ describe("Tests for getSegment service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.segment.findUnique.mockRejectedValue(errToThrow);
|
||||
prisma.segment.findUnique.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(getSegment(mockSegmentId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.segment.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.segment.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(getSegment(mockSegmentId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -227,7 +228,7 @@ describe("Tests for updateSegment service", () => {
|
||||
testInputValidation(updateSegment, "123", {});
|
||||
|
||||
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
|
||||
prismaMock.segment.findUnique.mockResolvedValue(null);
|
||||
prisma.segment.findUnique.mockResolvedValue(null);
|
||||
await expect(updateSegment(mockSegmentId, mockSegmentCreateInput)).rejects.toThrow(
|
||||
ResourceNotFoundError
|
||||
);
|
||||
@@ -240,14 +241,14 @@ describe("Tests for updateSegment service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.segment.update.mockRejectedValue(errToThrow);
|
||||
prisma.segment.update.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(updateSegment(mockSegmentId, mockSegmentCreateInput)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.segment.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.segment.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(updateSegment(mockSegmentId, mockSegmentCreateInput)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -257,7 +258,7 @@ describe("Tests for updateSegment service", () => {
|
||||
describe("Tests for deleteSegment service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Deletes a user segment", async () => {
|
||||
prismaMock.segment.delete.mockResolvedValue(mockSegmentPrisma);
|
||||
prisma.segment.delete.mockResolvedValue(mockSegmentPrisma);
|
||||
const result = await deleteSegment(mockSegmentId);
|
||||
expect(result).toEqual(mockSegment);
|
||||
});
|
||||
@@ -267,7 +268,7 @@ describe("Tests for deleteSegment service", () => {
|
||||
testInputValidation(deleteSegment, "123");
|
||||
|
||||
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
|
||||
prismaMock.segment.findUnique.mockResolvedValue(null);
|
||||
prisma.segment.findUnique.mockResolvedValue(null);
|
||||
await expect(deleteSegment(mockSegmentId)).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
|
||||
@@ -278,14 +279,14 @@ describe("Tests for deleteSegment service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.segment.delete.mockRejectedValue(errToThrow);
|
||||
prisma.segment.delete.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(deleteSegment(mockSegmentId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.segment.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.segment.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(deleteSegment(mockSegmentId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -295,7 +296,7 @@ describe("Tests for deleteSegment service", () => {
|
||||
describe("Tests for cloneSegment service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Clones a user segment", async () => {
|
||||
prismaMock.segment.create.mockResolvedValue({
|
||||
prisma.segment.create.mockResolvedValue({
|
||||
...mockSegmentPrisma,
|
||||
title: `Copy of ${mockSegmentPrisma.title}`,
|
||||
});
|
||||
@@ -311,7 +312,7 @@ describe("Tests for cloneSegment service", () => {
|
||||
testInputValidation(cloneSegment, "123", "123");
|
||||
|
||||
it("Throws a ResourceNotFoundError error if the user segment does not exist", async () => {
|
||||
prismaMock.segment.findUnique.mockResolvedValue(null);
|
||||
prisma.segment.findUnique.mockResolvedValue(null);
|
||||
await expect(cloneSegment(mockSegmentId, mockSurveyId)).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
|
||||
@@ -322,14 +323,14 @@ describe("Tests for cloneSegment service", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.segment.create.mockRejectedValue(errToThrow);
|
||||
prisma.segment.create.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(cloneSegment(mockSegmentId, mockSurveyId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for unexpected exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.segment.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.segment.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(cloneSegment(mockSegmentId, mockSurveyId)).rejects.toThrow(Error);
|
||||
});
|
||||
@@ -13,31 +13,30 @@ import { add, isAfter, parseISO } from "date-fns";
|
||||
import { access, mkdir, readFile, rmdir, unlink, writeFile } from "fs/promises";
|
||||
import { lookup } from "mime-types";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { join } from "path";
|
||||
import path from "path";
|
||||
import path, { join } from "path";
|
||||
|
||||
import { TAccessType } from "@formbricks/types/storage";
|
||||
|
||||
import { IS_S3_CONFIGURED, MAX_SIZES, UPLOADS_DIR, WEBAPP_URL } from "../constants";
|
||||
import {
|
||||
IS_S3_CONFIGURED,
|
||||
MAX_SIZES,
|
||||
S3_BUCKET_NAME,
|
||||
S3_REGION,
|
||||
UPLOADS_DIR,
|
||||
WEBAPP_URL,
|
||||
} from "../constants";
|
||||
import { generateLocalSignedUrl } from "../crypto";
|
||||
import { env } from "../env.mjs";
|
||||
import { env } from "../env";
|
||||
import { storageCache } from "./cache";
|
||||
|
||||
// global variables
|
||||
|
||||
const AWS_BUCKET_NAME = env.S3_BUCKET_NAME!;
|
||||
const AWS_REGION = env.S3_REGION!;
|
||||
const S3_ACCESS_KEY = env.S3_ACCESS_KEY!;
|
||||
const S3_SECRET_KEY = env.S3_SECRET_KEY!;
|
||||
|
||||
// S3Client Singleton
|
||||
|
||||
export const s3Client = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: S3_ACCESS_KEY,
|
||||
secretAccessKey: S3_SECRET_KEY!,
|
||||
accessKeyId: env.S3_ACCESS_KEY!,
|
||||
secretAccessKey: env.S3_SECRET_KEY!,
|
||||
},
|
||||
region: AWS_REGION!,
|
||||
region: S3_REGION!,
|
||||
});
|
||||
|
||||
const ensureDirectoryExists = async (dirPath: string) => {
|
||||
@@ -81,7 +80,7 @@ const getS3SignedUrl = async (fileKey: string): Promise<string> => {
|
||||
return unstable_cache(
|
||||
async () => {
|
||||
const getObjectCommand = new GetObjectCommand({
|
||||
Bucket: AWS_BUCKET_NAME,
|
||||
Bucket: S3_BUCKET_NAME,
|
||||
Key: fileKey,
|
||||
});
|
||||
|
||||
@@ -242,7 +241,7 @@ export const getS3UploadSignedUrl = async (
|
||||
try {
|
||||
const { fields, url } = await createPresignedPost(s3Client, {
|
||||
Expires: 10 * 60, // 10 minutes
|
||||
Bucket: AWS_BUCKET_NAME,
|
||||
Bucket: env.S3_BUCKET_NAME!,
|
||||
Key: `${environmentId}/${accessType}/${fileName}`,
|
||||
Fields: {
|
||||
"Content-Type": contentType,
|
||||
@@ -305,7 +304,7 @@ export const putFile = async (
|
||||
} else {
|
||||
const input = {
|
||||
Body: fileBuffer,
|
||||
Bucket: AWS_BUCKET_NAME,
|
||||
Bucket: S3_BUCKET_NAME,
|
||||
Key: `${environmentId}/${accessType}/${fileName}`,
|
||||
};
|
||||
|
||||
@@ -354,7 +353,7 @@ export const deleteLocalFile = async (filePath: string) => {
|
||||
|
||||
export const deleteS3File = async (fileKey: string) => {
|
||||
const deleteObjectCommand = new DeleteObjectCommand({
|
||||
Bucket: AWS_BUCKET_NAME,
|
||||
Bucket: S3_BUCKET_NAME,
|
||||
Key: fileKey,
|
||||
});
|
||||
|
||||
@@ -370,7 +369,7 @@ export const deleteS3FilesByEnvironmentId = async (environmentId: string) => {
|
||||
// List all objects in the bucket with the prefix of environmentId
|
||||
const listObjectsOutput = await s3Client.send(
|
||||
new ListObjectsCommand({
|
||||
Bucket: AWS_BUCKET_NAME,
|
||||
Bucket: S3_BUCKET_NAME,
|
||||
Prefix: environmentId,
|
||||
})
|
||||
);
|
||||
@@ -388,7 +387,7 @@ export const deleteS3FilesByEnvironmentId = async (environmentId: string) => {
|
||||
// Delete the objects
|
||||
await s3Client.send(
|
||||
new DeleteObjectsCommand({
|
||||
Bucket: AWS_BUCKET_NAME,
|
||||
Bucket: S3_BUCKET_NAME,
|
||||
Delete: {
|
||||
Objects: objectsToDelete,
|
||||
},
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
import { TTeam } from "@formbricks/types/teams";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
|
||||
import { selectPerson } from "../../person/service";
|
||||
import { selectSurvey } from "../service";
|
||||
import { selectPerson } from "../../../person/service";
|
||||
import { selectSurvey } from "../../service";
|
||||
|
||||
const currentDate = new Date();
|
||||
const fourDaysAgo = new Date();
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "../../__mocks__/database";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { prismaMock } from "@formbricks/database/src/jestClient";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
||||
|
||||
import { testInputValidation } from "../../vitestSetup";
|
||||
import {
|
||||
createSurvey,
|
||||
deleteSurvey,
|
||||
@@ -26,25 +29,18 @@ import {
|
||||
mockTransformedSurveyOutput,
|
||||
mockUser,
|
||||
updateSurveyInput,
|
||||
} from "./survey.mock";
|
||||
|
||||
// utility function to test input validation for all services
|
||||
const testInputValidation = async (service: Function, ...args: any[]): Promise<void> => {
|
||||
it("it should throw a ValidationError if the inputs are invalid", async () => {
|
||||
await expect(service(...args)).rejects.toThrow(ValidationError);
|
||||
});
|
||||
};
|
||||
} from "./__mock__/survey.mock";
|
||||
|
||||
describe("Tests for getSurvey", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns a survey", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
const survey = await getSurvey(mockId);
|
||||
expect(survey).toEqual(mockTransformedSurveyOutput);
|
||||
});
|
||||
|
||||
it("Returns null if survey is not found", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(null);
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(null);
|
||||
const survey = await getSurvey(mockId);
|
||||
expect(survey).toBeNull();
|
||||
});
|
||||
@@ -59,13 +55,13 @@ describe("Tests for getSurvey", () => {
|
||||
code: "P2002",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
prismaMock.survey.findUnique.mockRejectedValue(errToThrow);
|
||||
prisma.survey.findUnique.mockRejectedValue(errToThrow);
|
||||
await expect(getSurvey(mockId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prismaMock.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.findUnique.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(getSurvey(mockId)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -74,13 +70,13 @@ describe("Tests for getSurvey", () => {
|
||||
describe("Tests for getSurveysByActionClassId", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns an array of surveys for a given actionClassId", async () => {
|
||||
prismaMock.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
|
||||
prisma.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
|
||||
const surveys = await getSurveysByActionClassId(mockId);
|
||||
expect(surveys).toEqual([mockTransformedSurveyOutput]);
|
||||
});
|
||||
|
||||
it("Returns an empty array if no surveys are found", async () => {
|
||||
prismaMock.survey.findMany.mockResolvedValueOnce([]);
|
||||
prisma.survey.findMany.mockResolvedValueOnce([]);
|
||||
const surveys = await getSurveysByActionClassId(mockId);
|
||||
expect(surveys).toEqual([]);
|
||||
});
|
||||
@@ -91,7 +87,7 @@ describe("Tests for getSurveysByActionClassId", () => {
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(getSurveysByActionClassId(mockId)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -100,13 +96,13 @@ describe("Tests for getSurveysByActionClassId", () => {
|
||||
describe("Tests for getSurveys", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Returns an array of surveys for a given environmentId and page", async () => {
|
||||
prismaMock.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
|
||||
prisma.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
|
||||
const surveys = await getSurveys(mockId);
|
||||
expect(surveys).toEqual([mockTransformedSurveyOutput]);
|
||||
});
|
||||
|
||||
it("Returns an empty array if no surveys are found", async () => {
|
||||
prismaMock.survey.findMany.mockResolvedValueOnce([]);
|
||||
prisma.survey.findMany.mockResolvedValueOnce([]);
|
||||
|
||||
const surveys = await getSurveys(mockId);
|
||||
expect(surveys).toEqual([]);
|
||||
@@ -123,13 +119,13 @@ describe("Tests for getSurveys", () => {
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prismaMock.survey.findMany.mockRejectedValue(errToThrow);
|
||||
prisma.survey.findMany.mockRejectedValue(errToThrow);
|
||||
await expect(getSurveys(mockId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.findMany.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(getSurveys(mockId)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -137,12 +133,12 @@ describe("Tests for getSurveys", () => {
|
||||
|
||||
describe("Tests for updateSurvey", () => {
|
||||
beforeEach(() => {
|
||||
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
});
|
||||
describe("Happy Path", () => {
|
||||
it("Updates a survey successfully", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prismaMock.survey.update.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.update.mockResolvedValueOnce(mockSurveyOutput);
|
||||
const updatedSurvey = await updateSurvey(updateSurveyInput);
|
||||
expect(updatedSurvey).toEqual(mockTransformedSurveyOutput);
|
||||
});
|
||||
@@ -152,7 +148,7 @@ describe("Tests for updateSurvey", () => {
|
||||
testInputValidation(updateSurvey, "123");
|
||||
|
||||
it("Throws ResourceNotFoundError if the survey does not exist", async () => {
|
||||
prismaMock.survey.findUnique.mockRejectedValueOnce(
|
||||
prisma.survey.findUnique.mockRejectedValueOnce(
|
||||
new ResourceNotFoundError("Survey", updateSurveyInput.id)
|
||||
);
|
||||
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(ResourceNotFoundError);
|
||||
@@ -164,15 +160,15 @@ describe("Tests for updateSurvey", () => {
|
||||
code: "P2002",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prismaMock.survey.update.mockRejectedValue(errToThrow);
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.update.mockRejectedValue(errToThrow);
|
||||
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prismaMock.survey.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -181,7 +177,7 @@ describe("Tests for updateSurvey", () => {
|
||||
describe("Tests for deleteSurvey", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Deletes a survey successfully", async () => {
|
||||
prismaMock.survey.delete.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.delete.mockResolvedValueOnce(mockSurveyOutput);
|
||||
const deletedSurvey = await deleteSurvey(mockId);
|
||||
expect(deletedSurvey).toEqual(mockSurveyOutput);
|
||||
});
|
||||
@@ -192,8 +188,8 @@ describe("Tests for deleteSurvey", () => {
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prismaMock.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(deleteSurvey(mockId)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -201,14 +197,14 @@ describe("Tests for deleteSurvey", () => {
|
||||
|
||||
describe("Tests for createSurvey", () => {
|
||||
beforeEach(() => {
|
||||
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
});
|
||||
|
||||
describe("Happy Path", () => {
|
||||
it("Creates a survey successfully", async () => {
|
||||
prismaMock.survey.create.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prismaMock.team.findFirst.mockResolvedValueOnce(mockTeamOutput);
|
||||
prismaMock.user.findMany.mockResolvedValueOnce([
|
||||
prisma.survey.create.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.team.findFirst.mockResolvedValueOnce(mockTeamOutput);
|
||||
prisma.user.findMany.mockResolvedValueOnce([
|
||||
{
|
||||
...mockUser,
|
||||
twoFactorSecret: null,
|
||||
@@ -219,7 +215,7 @@ describe("Tests for createSurvey", () => {
|
||||
role: "engineer",
|
||||
},
|
||||
]);
|
||||
prismaMock.user.update.mockResolvedValueOnce({
|
||||
prisma.user.update.mockResolvedValueOnce({
|
||||
...mockUser,
|
||||
twoFactorSecret: null,
|
||||
backupCodes: null,
|
||||
@@ -238,7 +234,7 @@ describe("Tests for createSurvey", () => {
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.delete.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(createSurvey(mockId, createSurveyInput)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -246,13 +242,13 @@ describe("Tests for createSurvey", () => {
|
||||
|
||||
describe("Tests for duplicateSurvey", () => {
|
||||
beforeEach(() => {
|
||||
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
});
|
||||
|
||||
describe("Happy Path", () => {
|
||||
it("Duplicates a survey successfully", async () => {
|
||||
prismaMock.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prismaMock.survey.create.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
|
||||
prisma.survey.create.mockResolvedValueOnce(mockSurveyOutput);
|
||||
const createdSurvey = await duplicateSurvey(mockId, mockId, mockId);
|
||||
expect(createdSurvey).toEqual(mockSurveyOutput);
|
||||
});
|
||||
@@ -262,13 +258,13 @@ describe("Tests for duplicateSurvey", () => {
|
||||
testInputValidation(duplicateSurvey, "123", "123");
|
||||
|
||||
it("Throws ResourceNotFoundError if the survey does not exist", async () => {
|
||||
prismaMock.survey.findUnique.mockRejectedValueOnce(new ResourceNotFoundError("Survey", mockId));
|
||||
prisma.survey.findUnique.mockRejectedValueOnce(new ResourceNotFoundError("Survey", mockId));
|
||||
await expect(duplicateSurvey(mockId, mockId, mockId)).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.survey.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.survey.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(duplicateSurvey(mockId, mockId, mockId)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -277,21 +273,21 @@ describe("Tests for duplicateSurvey", () => {
|
||||
describe("Tests for getSyncedSurveys", () => {
|
||||
describe("Happy Path", () => {
|
||||
beforeEach(() => {
|
||||
prismaMock.product.findFirst.mockResolvedValueOnce(mockProduct);
|
||||
prismaMock.display.findMany.mockResolvedValueOnce([mockDisplay]);
|
||||
prismaMock.attributeClass.findMany.mockResolvedValueOnce([mockAttributeClass]);
|
||||
prisma.product.findFirst.mockResolvedValueOnce(mockProduct);
|
||||
prisma.display.findMany.mockResolvedValueOnce([mockDisplay]);
|
||||
prisma.attributeClass.findMany.mockResolvedValueOnce([mockAttributeClass]);
|
||||
});
|
||||
|
||||
it("Returns synced surveys", async () => {
|
||||
prismaMock.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
|
||||
prismaMock.person.findUnique.mockResolvedValueOnce(mockPerson);
|
||||
prisma.survey.findMany.mockResolvedValueOnce([mockSurveyOutput]);
|
||||
prisma.person.findUnique.mockResolvedValueOnce(mockPerson);
|
||||
const surveys = await getSyncSurveys(mockId, mockPerson.id);
|
||||
expect(surveys).toEqual([mockTransformedSurveyOutput]);
|
||||
});
|
||||
|
||||
it("Returns an empty array if no surveys are found", async () => {
|
||||
prismaMock.survey.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.person.findUnique.mockResolvedValueOnce(mockPerson);
|
||||
prisma.survey.findMany.mockResolvedValueOnce([]);
|
||||
prisma.person.findUnique.mockResolvedValueOnce(mockPerson);
|
||||
const surveys = await getSyncSurveys(mockId, mockPerson.id);
|
||||
expect(surveys).toEqual([]);
|
||||
});
|
||||
@@ -301,15 +297,15 @@ describe("Tests for getSyncedSurveys", () => {
|
||||
testInputValidation(getSyncSurveys, "123", {});
|
||||
|
||||
it("does not find a Product", async () => {
|
||||
prismaMock.product.findFirst.mockResolvedValueOnce(null);
|
||||
prisma.product.findFirst.mockResolvedValueOnce(null);
|
||||
|
||||
await expect(getSyncSurveys(mockId, mockPerson.id)).rejects.toThrow(Error);
|
||||
});
|
||||
|
||||
it("should throw an error if there is an unknown error", async () => {
|
||||
const mockErrorMessage = "Unknown error occurred";
|
||||
prismaMock.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
prismaMock.survey.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
prisma.actionClass.findMany.mockResolvedValueOnce([mockActionClass]);
|
||||
prisma.survey.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
await expect(getSyncSurveys(mockId, mockPerson.id)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
@@ -2,13 +2,10 @@
|
||||
and how we can improve it. All data including the IP address is collected anonymously
|
||||
and we cannot trace anything back to you or your customers. If you still want to
|
||||
disable telemetry, set the environment variable TELEMETRY_DISABLED=1 */
|
||||
import { env } from "./env";
|
||||
|
||||
export const captureTelemetry = async (eventName: string, properties = {}) => {
|
||||
if (
|
||||
process.env.TELEMETRY_DISABLED !== "1" &&
|
||||
process.env.NODE_ENV === "production" &&
|
||||
process.env.INSTANCE_ID
|
||||
) {
|
||||
if (env.TELEMETRY_DISABLED !== "1" && process.env.NODE_ENV === "production" && process.env.INSTANCE_ID) {
|
||||
try {
|
||||
await fetch("https://eu.posthog.com/capture/", {
|
||||
method: "POST",
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/js-library.json",
|
||||
"extends": "@formbricks/tsconfig/nextjs.json",
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"downlevelIteration": true
|
||||
"downlevelIteration": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@prisma/client/*": ["@formbricks/database/client/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ export const createUser = async (data: TUserCreateInput): Promise<TUser> => {
|
||||
select: responseSelection,
|
||||
});
|
||||
|
||||
console.log("user", user);
|
||||
|
||||
userCache.revalidate({
|
||||
email: user.email,
|
||||
id: user.id,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import cuid2 from "@paralleldrive/cuid2";
|
||||
|
||||
import { decryptAES128, symmetricDecrypt, symmetricEncrypt } from "../../lib/crypto";
|
||||
import { env } from "../../lib/env.mjs";
|
||||
import { env } from "../../lib/env";
|
||||
|
||||
// generate encrypted single use id for the survey
|
||||
export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
|
||||
|
||||
7
packages/lib/vite.config.ts
Normal file
7
packages/lib/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
setupFiles: ["./vitestSetup.ts"],
|
||||
},
|
||||
});
|
||||
@@ -1,25 +1,29 @@
|
||||
// mock these globally used functions
|
||||
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
||||
|
||||
import { ValidationError } from "@formbricks/types/errors";
|
||||
|
||||
jest.mock("next/cache", () => ({
|
||||
vi.mock("next/cache", () => ({
|
||||
__esModule: true,
|
||||
unstable_cache: (fn: () => {}) => {
|
||||
return async () => {
|
||||
return fn();
|
||||
};
|
||||
},
|
||||
revalidateTag: jest.fn(),
|
||||
revalidateTag: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("server-only", () => jest.fn());
|
||||
vi.mock("server-only", () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
vi.resetModules();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
export const testInputValidation = async (service: Function, ...args: any[]): Promise<void> => {
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"prettier": "^3.2.4",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/surveys",
|
||||
"license": "MIT",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.0",
|
||||
"description": "Formbricks-surveys is a helper library to embed surveys into your application",
|
||||
"homepage": "https://formbricks.com",
|
||||
"repository": {
|
||||
@@ -37,24 +37,27 @@
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/embed-snippet": "1.1.2",
|
||||
"@calcom/embed-snippet": "1.1.3",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@preact/preset-vite": "^2.8.1",
|
||||
"isomorphic-dompurify": "^2.3.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"concurrently": "8.2.2",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "1.10.12",
|
||||
"postcss": "^8.4.33",
|
||||
"preact": "^10.19.3",
|
||||
"isomorphic-dompurify": "^2.4.0",
|
||||
"postcss": "^8.4.35",
|
||||
"preact": "^10.19.6",
|
||||
"react-date-picker": "^10.6.0",
|
||||
"serve": "14.2.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"terser": "^5.27.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-dts": "^3.7.2",
|
||||
"terser": "^5.28.1",
|
||||
"vite": "^5.1.4",
|
||||
"vite-plugin-dts": "^3.7.3",
|
||||
"vite-tsconfig-paths": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"plugins": [{ "name": "next" }],
|
||||
"moduleResolution": "bundler",
|
||||
"allowJs": true,
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
@@ -14,7 +15,7 @@
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": false,
|
||||
"target": "es5"
|
||||
"target": "esnext"
|
||||
},
|
||||
"include": ["src", "next-env.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
"clean": "rimraf node_modules dist turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.11.6",
|
||||
"@types/react": "18.2.48",
|
||||
"@types/react-dom": "18.2.18",
|
||||
"@types/node": "20.11.20",
|
||||
"@types/react": "18.2.58",
|
||||
"@types/react-dom": "18.2.19",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@formbricks/tsconfig": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import posthog from "posthog-js";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { env } from "@formbricks/lib/env.mjs";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"boring-avatars": "^1.10.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
|
||||
5407
pnpm-lock.yaml
generated
5407
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -106,9 +106,15 @@
|
||||
"NEXTAUTH_SECRET",
|
||||
"NEXTAUTH_URL",
|
||||
"NODE_ENV",
|
||||
"OIDC_CLIENT_ID",
|
||||
"OIDC_CLIENT_SECRET",
|
||||
"OIDC_DISPLAY_NAME",
|
||||
"OIDC_ISSUER",
|
||||
"OIDC_SIGNING_ALGORITHM",
|
||||
"PASSWORD_RESET_DISABLED",
|
||||
"PLAYWRIGHT_CI",
|
||||
"PRIVACY_URL",
|
||||
"RATE_LIMITING_DISABLED",
|
||||
"S3_ACCESS_KEY",
|
||||
"S3_SECRET_KEY",
|
||||
"S3_REGION",
|
||||
|
||||
1
vitest.workspace.ts
Normal file
1
vitest.workspace.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default ["packages/*"];
|
||||
Reference in New Issue
Block a user