This commit is contained in:
Naitik
2023-10-04 12:09:53 +05:30
50 changed files with 462 additions and 490 deletions

View File

@@ -12,7 +12,7 @@ import {
getActionCountInLast7Days,
getActionCountInLastHour,
} from "@formbricks/lib/services/actions";
import { getSurveysByActionClassId } from "@formbricks/lib/services/survey";
import { getSurveysByActionClassId } from "@formbricks/lib/survey/service";
import { AuthorizationError } from "@formbricks/types/v1/errors";
export async function deleteActionClassAction(environmentId, actionClassId: string) {

View File

@@ -1,6 +1,6 @@
"use server";
import { getSurveysByAttributeClassId } from "@formbricks/lib/services/survey";
import { getSurveysByAttributeClassId } from "@formbricks/lib/survey/service";
export const GetActiveInactiveSurveysAction = async (
attributeClassId: string

View File

@@ -1,133 +1,45 @@
"use server";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { prisma } from "@formbricks/database";
import { ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { INTERNAL_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { deleteSurvey, getSurvey } from "@formbricks/lib/services/survey";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createMembership } from "@formbricks/lib/services/membership";
import { createProduct } from "@formbricks/lib/services/product";
import { createTeam, getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { deleteSurvey, getSurvey } from "@formbricks/lib/survey/service";
import { AuthorizationError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { Team } from "@prisma/client";
import { Prisma as prismaClient } from "@prisma/client/";
import { createProduct } from "@formbricks/lib/services/product";
import { getServerSession } from "next-auth";
export async function createTeam(teamName: string, ownerUserId: string): Promise<Team> {
const newTeam = await prisma.team.create({
data: {
name: teamName,
memberships: {
create: {
user: { connect: { id: ownerUserId } },
role: "owner",
accepted: true,
},
},
products: {
create: [
{
name: "My Product",
environments: {
create: [
{
type: "production",
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: "automatic",
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: "automatic",
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: "automatic",
},
],
},
attributeClasses: {
create: [
{
name: "userId",
description: "The internal ID of the person",
type: "automatic",
},
{
name: "email",
description: "The email of the person",
type: "automatic",
},
],
},
},
{
type: "development",
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: "automatic",
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: "automatic",
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: "automatic",
},
],
},
attributeClasses: {
create: [
{
name: "userId",
description: "The internal ID of the person",
type: "automatic",
},
{
name: "email",
description: "The email of the person",
type: "automatic",
},
],
},
},
],
},
},
],
},
},
include: {
memberships: true,
products: {
include: {
environments: true,
},
},
},
export async function createTeamAction(teamName: string): Promise<Team> {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const newTeam = await createTeam({
name: teamName,
});
const teamId = newTeam?.id;
await createMembership(newTeam.id, session.user.id, {
role: "owner",
accepted: true,
});
if (teamId) {
fetch(`${WEBAPP_URL}/api/v1/teams/${teamId}/add_demo_product`, {
method: "POST",
headers: {
"x-api-key": INTERNAL_SECRET,
},
});
}
await createProduct(newTeam.id, {
name: "My Product",
});
return newTeam;
}
export async function duplicateSurveyAction(environmentId: string, surveyId: string) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const existingSurvey = await getSurvey(surveyId);
if (!existingSurvey) {
@@ -180,6 +92,24 @@ export async function copyToOtherEnvironmentAction(
surveyId: string,
targetEnvironmentId: string
) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorizedToAccessSourceEnvironment = await hasUserEnvironmentAccess(
session.user.id,
environmentId
);
if (!isAuthorizedToAccessSourceEnvironment) throw new AuthorizationError("Not authorized");
const isAuthorizedToAccessTargetEnvironment = await hasUserEnvironmentAccess(
session.user.id,
targetEnvironmentId
);
if (!isAuthorizedToAccessTargetEnvironment) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const existingSurvey = await prisma.survey.findFirst({
where: {
id: surveyId,
@@ -305,12 +235,32 @@ export async function copyToOtherEnvironmentAction(
}
export const deleteSurveyAction = async (surveyId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
await deleteSurvey(surveyId);
};
export const createProductAction = async (environmentId: string, productName: string) => {
const productCreated = await createProduct(environmentId, productName);
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const newEnvironment = productCreated.environments[0];
return newEnvironment;
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const team = await getTeamByEnvironmentId(environmentId);
if (!team) throw new ResourceNotFoundError("Team from environment", environmentId);
const product = await createProduct(team.id, {
name: productName,
});
// get production environment
const productionEnvironment = product.environments.find((environment) => environment.type === "production");
if (!productionEnvironment) throw new ResourceNotFoundError("Production environment", environmentId);
return productionEnvironment;
};

View File

@@ -2,7 +2,7 @@ import GoogleSheetWrapper from "@/app/(app)/environments/[environmentId]/integra
import GoBackButton from "@/components/shared/GoBackButton";
import { getSpreadSheets } from "@formbricks/lib/services/googleSheet";
import { getIntegrations } from "@formbricks/lib/services/integrations";
import { getSurveys } from "@formbricks/lib/services/survey";
import { getSurveys } from "@formbricks/lib/survey/service";
import { TGoogleSheetIntegration, TGoogleSpreadsheet } from "@formbricks/types/v1/integrations";
import {
GOOGLE_SHEETS_CLIENT_ID,

View File

@@ -4,7 +4,7 @@ import WebhookRowData from "@/app/(app)/environments/[environmentId]/integration
import WebhookTable from "@/app/(app)/environments/[environmentId]/integrations/webhooks/WebhookTable";
import WebhookTableHeading from "@/app/(app)/environments/[environmentId]/integrations/webhooks/WebhookTableHeading";
import GoBackButton from "@/components/shared/GoBackButton";
import { getSurveys } from "@formbricks/lib/services/survey";
import { getSurveys } from "@formbricks/lib/survey/service";
import { getWebhooks } from "@formbricks/lib/services/webhook";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";

View File

@@ -1,6 +1,6 @@
import ResponseTimeline from "@/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseTimeline";
import { getSurveys } from "@formbricks/lib/survey/service";
import { getResponsesByPersonId } from "@formbricks/lib/response/service";
import { getSurveys } from "@formbricks/lib/services/survey";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TResponseWithSurvey } from "@formbricks/types/v1/responses";
import { TSurvey } from "@formbricks/types/v1/surveys";

View File

@@ -1,6 +1,6 @@
"use client";
import { updateTeamAction } from "@/app/(app)/environments/[environmentId]/settings/members/actions";
import { updateTeamNameAction } from "@/app/(app)/environments/[environmentId]/settings/members/actions";
import { TTeam } from "@formbricks/types/v1/teams";
import { Button, Input, Label } from "@formbricks/ui";
import { useRouter } from "next/navigation";
@@ -43,7 +43,7 @@ export default function EditTeamName({ team }: TEditTeamNameProps) {
const handleUpdateTeamName: SubmitHandler<TEditTeamNameForm> = async (data) => {
try {
setIsUpdatingTeam(true);
await updateTeamAction(team.id, data);
await updateTeamNameAction(team.id, data.name);
setIsUpdatingTeam(false);
toast.success("Team name updated successfully.");

View File

@@ -20,13 +20,22 @@ import {
import { deleteTeam, updateTeam } from "@formbricks/lib/services/team";
import { TInviteUpdateInput } from "@formbricks/types/v1/invites";
import { TMembershipRole, TMembershipUpdateInput } from "@formbricks/types/v1/memberships";
import { TTeamUpdateInput } from "@formbricks/types/v1/teams";
import { getServerSession } from "next-auth";
import { hasTeamAccess, hasTeamAuthority, hasTeamOwnership, isOwner } from "@formbricks/lib/auth";
import { INVITE_DISABLED } from "@formbricks/lib/constants";
export const updateTeamAction = async (teamId: string, data: TTeamUpdateInput) => {
return await updateTeam(teamId, data);
export const updateTeamNameAction = async (teamId: string, teamName: string) => {
const session = await getServerSession(authOptions);
if (!session) {
throw new AuthenticationError("Not authenticated");
}
const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId);
if (!isUserAuthorized) {
throw new AuthenticationError("Not authorized");
}
return await updateTeam(teamId, { name: teamName });
};
export const updateMembershipAction = async (

View File

@@ -5,7 +5,7 @@ import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
import { SURVEY_BASE_URL } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getSurveys } from "@formbricks/lib/services/survey";
import { getSurveys } from "@formbricks/lib/survey/service";
import type { TEnvironment } from "@formbricks/types/v1/environment";
import { Badge } from "@formbricks/ui";
import { ComputerDesktopIcon, LinkIcon, PlusIcon } from "@heroicons/react/24/solid";

View File

@@ -4,6 +4,7 @@ import TemplateList from "@/app/(app)/environments/[environmentId]/surveys/templ
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import type { TEnvironment } from "@formbricks/types/v1/environment";
import type { TProduct } from "@formbricks/types/v1/product";
import { TSurveyInput } from "@formbricks/types/v1/surveys";
import { TTemplate } from "@formbricks/types/v1/templates";
import { useRouter } from "next/navigation";
import { useState } from "react";
@@ -28,7 +29,7 @@ export default function SurveyStarter({
...template.preset,
type: surveyType,
autoComplete,
};
} as Partial<TSurveyInput>;
try {
const survey = await createSurveyAction(environmentId, augmentedTemplate);
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);

View File

@@ -1,7 +1,7 @@
import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service";
import { getSurveyResponses } from "@formbricks/lib/response/service";
import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
export const getAnalysisData = async (surveyId: string, environmentId: string) => {

View File

@@ -23,7 +23,7 @@ import LinkSurveyShareButton from "@/app/(app)/environments/[environmentId]/surv
import SurveyStatusDropdown from "@/components/shared/SurveyStatusDropdown";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TProduct } from "@formbricks/types/v1/product";
import { surveyMutateAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { updateSurveyAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
interface SummaryHeaderProps {
surveyId: string;
@@ -108,7 +108,7 @@ const SummaryHeader = ({
value={survey.status}
onValueChange={(value) => {
const castedValue = value as "draft" | "inProgress" | "paused" | "completed";
surveyMutateAction({ ...survey, status: castedValue })
updateSurveyAction({ ...survey, status: castedValue })
.then(() => {
toast.success(
value === "inProgress"

View File

@@ -14,7 +14,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { validateQuestion } from "./Validation";
import { deleteSurveyAction, surveyMutateAction } from "./actions";
import { deleteSurveyAction, updateSurveyAction } from "./actions";
interface SurveyMenuBarProps {
localSurvey: TSurveyWithAnalytics;
@@ -143,7 +143,7 @@ export default function SurveyMenuBar({
}
try {
await surveyMutateAction({ ...strippedSurvey });
await updateSurveyAction({ ...strippedSurvey });
router.refresh();
setIsMutatingSurvey(false);
toast.success("Changes saved.");
@@ -242,7 +242,7 @@ export default function SurveyMenuBar({
if (!validateSurvey(localSurvey)) {
return;
}
await surveyMutateAction({ ...localSurvey, status: "inProgress" });
await updateSurveyAction({ ...localSurvey, status: "inProgress" });
router.refresh();
setIsMutatingSurvey(false);
router.push(`/environments/${environment.id}/surveys/${localSurvey.id}/summary?success=true`);

View File

@@ -1,12 +1,28 @@
"use server";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { deleteSurvey, updateSurvey } from "@formbricks/lib/services/survey";
import { deleteSurvey, updateSurvey } from "@formbricks/lib/survey/service";
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { AuthorizationError } from "@formbricks/types/v1/errors";
export async function updateSurveyAction(survey: TSurvey): Promise<TSurvey> {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, survey.id);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
export async function surveyMutateAction(survey: TSurvey): Promise<TSurvey> {
return await updateSurvey(survey);
}
export async function deleteSurveyAction(surveyId: string) {
export const deleteSurveyAction = async (surveyId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
await deleteSurvey(surveyId);
}
};

View File

@@ -2,7 +2,7 @@ export const revalidate = REVALIDATION_INTERVAL;
import React from "react";
import { FORMBRICKS_ENCRYPTION_KEY, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import SurveyEditor from "./SurveyEditor";
import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey";
import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getActionClasses } from "@formbricks/lib/actionClass/service";

View File

@@ -1,7 +1,18 @@
"use server";
import { createSurvey } from "@formbricks/lib/services/survey";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createSurvey } from "@formbricks/lib/survey/service";
import { AuthorizationError } from "@formbricks/types/v1/errors";
import { TSurvey } from "@formbricks/types/v1/surveys";
export async function createSurveyAction(environmentId: string, surveyBody: Partial<TSurvey>) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
export async function createSurveyAction(environmentId: string, surveyBody: any) {
return await createSurvey(environmentId, surveyBody);
}

View File

@@ -22,6 +22,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { createSurveyAction } from "./actions";
import { customSurvey, templates } from "./templates";
import { TSurveyInput } from "@formbricks/types/v1/surveys";
type TemplateList = {
environmentId: string;
@@ -77,7 +78,7 @@ export default function TemplateList({
...activeTemplate.preset,
type: surveyType,
autoComplete,
};
} as Partial<TSurveyInput>;
const survey = await createSurveyAction(environmentId, augmentedTemplate);
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);
};

View File

@@ -1,7 +1,18 @@
"use server";
import { createSurvey } from "@formbricks/lib/services/survey";
import { createSurvey } from "@formbricks/lib/survey/service";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { AuthorizationError } from "@formbricks/types/v1/errors";
import { TSurvey } from "@formbricks/types/v1/surveys";
export async function createSurveyAction(environmentId: string, surveyBody: Partial<TSurvey>) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
export async function createSurveyAction(environmentId: string, surveyBody: any) {
return await createSurvey(environmentId, surveyBody);
}

View File

@@ -41,7 +41,11 @@ const Objective: React.FC<ObjectiveProps> = ({ next, skip, formbricksResponseId,
if (selectedObjective) {
try {
setIsProfileUpdating(true);
const updatedProfile = { ...profile, objective: selectedObjective.id };
const updatedProfile = {
...profile,
objective: selectedObjective.id,
name: profile.name ?? undefined,
};
await updateProfileAction(updatedProfile);
setIsProfileUpdating(false);
} catch (e) {

View File

@@ -1,23 +1,25 @@
export const revalidate = REVALIDATION_INTERVAL;
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProfile } from "@formbricks/lib/services/profile";
import { getServerSession } from "next-auth";
import Onboarding from "./components/Onboarding";
import { getEnvironmentByUser } from "@formbricks/lib/services/environment";
import { getProfile } from "@formbricks/lib/services/profile";
import { ErrorComponent } from "@formbricks/ui";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
export default async function OnboardingPage() {
const session = await getServerSession(authOptions);
const environment = await getEnvironmentByUser(session?.user);
if (!session) {
throw new Error("No session found");
}
const environment = await getFirstEnvironmentByUserId(session?.user.id);
const profile = await getProfile(session?.user.id!);
const product = await getProductByEnvironmentId(environment?.id!);
if (!environment || !profile || !product) {
return <ErrorComponent />;
throw new Error("Failed to get environment, profile, or product");
}
return <Onboarding session={session} environmentId={environment?.id} profile={profile} product={product} />;
return <Onboarding session={session} environmentId={environment.id} profile={profile} product={product} />;
}

View File

@@ -1,5 +1,5 @@
import { writeData } from "@formbricks/lib/services/googleSheet";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { TGoogleSheetIntegration, TIntegration } from "@formbricks/types/v1/integrations";
import { TPipelineInput } from "@formbricks/types/v1/pipelines";

View File

@@ -3,7 +3,7 @@ import { transformErrorToDetails } from "@/lib/api/validator";
import { InvalidInputError } from "@formbricks/types/v1/errors";
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { createDisplay } from "@formbricks/lib/services/displays";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getTeamDetails } from "@formbricks/lib/services/teamDetails";
import { TDisplay, ZDisplayInput } from "@formbricks/types/v1/displays";
import { NextResponse } from "next/server";

View File

@@ -2,8 +2,8 @@ import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { sendToPipeline } from "@/lib/pipelines";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { getSurvey } from "@formbricks/lib/survey/service";
import { updateResponse } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/services/survey";
import { ZResponseUpdateInput } from "@formbricks/types/v1/responses";
import { NextResponse } from "next/server";

View File

@@ -3,8 +3,8 @@ import { transformErrorToDetails } from "@/lib/api/validator";
import { sendToPipeline } from "@/lib/pipelines";
import { InvalidInputError } from "@formbricks/types/v1/errors";
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { getSurvey } from "@formbricks/lib/survey/service";
import { createResponse } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getTeamDetails } from "@formbricks/lib/services/teamDetails";
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/v1/responses";
import { NextResponse } from "next/server";

View File

@@ -1,5 +1,5 @@
import { prisma } from "@formbricks/database";
import { selectSurvey } from "@formbricks/lib/services/survey";
import { selectSurvey } from "@formbricks/lib/survey/service";
import { TPerson } from "@formbricks/types/v1/people";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { unstable_cache } from "next/cache";

View File

@@ -4,7 +4,7 @@ import { transformErrorToDetails } from "@/lib/api/validator";
import { deleteResponse, getResponse, updateResponse } from "@formbricks/lib/response/service";
import { TResponse, ZResponseUpdateInput } from "@formbricks/types/v1/responses";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { authenticateRequest } from "@/app/api/v1/auth";
import { handleErrorResponse } from "@/app/api/v1/auth";

View File

@@ -1,6 +1,6 @@
import { responses } from "@/lib/api/response";
import { NextResponse } from "next/server";
import { getSurvey, updateSurvey, deleteSurvey } from "@formbricks/lib/services/survey";
import { getSurvey, updateSurvey, deleteSurvey } from "@formbricks/lib/survey/service";
import { TSurvey, ZSurveyInput } from "@formbricks/types/v1/surveys";
import { transformErrorToDetails } from "@/lib/api/validator";
import { authenticateRequest } from "@/app/api/v1/auth";

View File

@@ -2,7 +2,7 @@ import { responses } from "@/lib/api/response";
import { authenticateRequest } from "@/app/api/v1/auth";
import { NextResponse } from "next/server";
import { transformErrorToDetails } from "@/lib/api/validator";
import { createSurvey, getSurveys } from "@formbricks/lib/services/survey";
import { createSurvey, getSurveys } from "@formbricks/lib/survey/service";
import { ZSurveyInput } from "@formbricks/types/v1/surveys";
import { DatabaseError } from "@formbricks/types/v1/errors";

View File

@@ -1,16 +1,13 @@
import { sendInviteAcceptedEmail, sendVerificationEmail } from "@/lib/email";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { populateEnvironment } from "@/lib/populate";
import { prisma } from "@formbricks/database";
import { EMAIL_VERIFICATION_DISABLED, INVITE_DISABLED, SIGNUP_ENABLED } from "@formbricks/lib/constants";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { deleteInvite } from "@formbricks/lib/services/invite";
import { createMembership } from "@formbricks/lib/services/membership";
import { createProduct } from "@formbricks/lib/services/product";
import { createProfile } from "@formbricks/lib/services/profile";
import { createTeam } from "@formbricks/lib/services/team";
import { NextResponse } from "next/server";
import { Prisma } from "@prisma/client";
import {
EMAIL_VERIFICATION_DISABLED,
INTERNAL_SECRET,
INVITE_DISABLED,
SIGNUP_ENABLED,
WEBAPP_URL,
} from "@formbricks/lib/constants";
export async function POST(request: Request) {
let { inviteToken, ...user } = await request.json();
@@ -22,7 +19,6 @@ export async function POST(request: Request) {
let inviteId;
try {
let data: Prisma.UserCreateArgs;
let invite;
if (inviteToken) {
@@ -40,92 +36,35 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Invalid invite ID" }, { status: 400 });
}
data = {
data: {
...user,
memberships: {
create: {
accepted: true,
role: invite.role,
team: {
connect: {
id: invite.teamId,
},
},
},
},
},
};
} else {
data = {
data: {
...user,
memberships: {
create: [
{
accepted: true,
role: "owner",
team: {
create: {
name: `${user.name}'s Team`,
products: {
create: [
{
name: "My Product",
environments: {
create: [
{
type: "production",
...populateEnvironment,
},
{
type: "development",
...populateEnvironment,
},
],
},
},
],
},
},
},
},
],
},
},
};
}
// create a user and assign him to the team
type UserWithMemberships = Prisma.UserGetPayload<{ include: { memberships: true } }>;
const userData = (await prisma.user.create({
...data,
include: {
memberships: true,
},
// TODO: This is a hack to get the correct types (casting), we should find a better way to do this
})) as UserWithMemberships;
const teamId = userData.memberships[0].teamId;
if (teamId) {
fetch(`${WEBAPP_URL}/api/v1/teams/${teamId}/add_demo_product`, {
method: "POST",
headers: {
"x-api-key": INTERNAL_SECRET,
},
const profile = await createProfile(user);
await createMembership(invite.teamId, profile.id, {
accepted: true,
role: invite.role,
});
}
if (inviteId) {
sendInviteAcceptedEmail(invite.creator.name, user.name, invite.creator.email);
await prisma.invite.delete({ where: { id: inviteId } });
}
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(profile);
}
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(userData);
await sendInviteAcceptedEmail(invite.creator.name, user.name, invite.creator.email);
await deleteInvite(inviteId);
return NextResponse.json(profile);
} else {
const team = await createTeam({
name: `${user.name}'s Team`,
});
await createProduct(team.id, { name: "My Product" });
const profile = await createProfile(user);
await createMembership(team.id, profile.id, { role: "owner", accepted: true });
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(profile);
}
return NextResponse.json(profile);
}
return NextResponse.json(userData);
} catch (e) {
if (e.code === "P2002") {
return NextResponse.json(

View File

@@ -1,6 +1,6 @@
import ClientLogout from "@/app/ClientLogout";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getEnvironmentByUser } from "@formbricks/lib/services/environment";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/services/environment";
import type { Session } from "next-auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
@@ -18,7 +18,10 @@ export default async function Home() {
let environment;
try {
environment = await getEnvironmentByUser(session?.user);
environment = await getFirstEnvironmentByUserId(session?.user.id);
if (!environment) {
throw new Error("No environment found");
}
} catch (error) {
console.error("error getting environment", error);
}

View File

@@ -5,7 +5,7 @@ import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
import { REVALIDATION_INTERVAL, WEBAPP_URL } from "@formbricks/lib/constants";
import { getOrCreatePersonByUserId } from "@formbricks/lib/services/person";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getEmailVerificationStatus } from "./helpers";
import { checkValidity } from "@/app/s/[surveyId]/prefilling";
import { notFound } from "next/navigation";

View File

@@ -2,7 +2,7 @@ import { cn } from "@formbricks/lib/cn";
import SurveyNavBarName from "@/components/shared/SurveyNavBarName";
import Link from "next/link";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
interface SecondNavbarProps {
tabs: { id: string; label: string; href: string; icon?: React.ReactNode }[];

View File

@@ -1,6 +1,6 @@
"use client";
import { surveyMutateAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { updateSurveyAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TSurvey } from "@formbricks/types/v1/surveys";
@@ -44,7 +44,7 @@ export default function SurveyStatusDropdown({
disabled={isStatusChangeDisabled}
onValueChange={(value) => {
const castedValue = value as "draft" | "inProgress" | "paused" | "completed";
surveyMutateAction({ ...survey, status: castedValue })
updateSurveyAction({ ...survey, status: castedValue })
.then(() => {
toast.success(
value === "inProgress"

View File

@@ -1,6 +1,5 @@
import { createTeam } from "@/app/(app)/environments/[environmentId]/actions";
import { createTeamAction } from "@/app/(app)/environments/[environmentId]/actions";
import Modal from "@/components/shared/Modal";
import { useProfile } from "@/lib/profile";
import { Button, Input, Label } from "@formbricks/ui";
import { PlusCircleIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/navigation";
@@ -15,13 +14,12 @@ interface CreateTeamModalProps {
export default function CreateTeamModal({ open, setOpen }: CreateTeamModalProps) {
const router = useRouter();
const { profile } = useProfile();
const [loading, setLoading] = useState(false);
const { register, handleSubmit } = useForm();
const submitTeam = async (data) => {
setLoading(true);
const newTeam = await createTeam(data.name, (profile as any).id);
const newTeam = await createTeamAction(data.name);
toast.success("Team created successfully!");
router.push(`/teams/${newTeam.id}`);

View File

@@ -1,28 +0,0 @@
import { EventType } from "@prisma/client";
export const populateEnvironment = {
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: EventType.automatic,
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: EventType.automatic,
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: EventType.automatic,
},
],
},
attributeClasses: {
create: [
{ name: "userId", description: "The internal ID of the person", type: EventType.automatic },
{ name: "email", description: "The email of the person", type: EventType.automatic },
],
},
};

View File

@@ -1,7 +1,6 @@
import { getSessionUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
import { prisma } from "@formbricks/database";
import { EnvironmentType } from "@prisma/client";
import { populateEnvironment } from "@/lib/populate";
import { createProduct } from "@formbricks/lib/services/product";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
@@ -93,29 +92,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
}
// Create a new product and associate it with the current team
const newProduct = await prisma.product.create({
data: {
name,
team: {
connect: { id: environment.product.teamId },
},
environments: {
create: [
{
type: EnvironmentType.production,
...populateEnvironment,
},
{
type: EnvironmentType.development,
...populateEnvironment,
},
],
},
},
select: {
environments: true,
},
});
const newProduct = await createProduct(environment.product.teamId, { name });
const firstEnvironment = newProduct.environments[0];
res.json(firstEnvironment);

View File

@@ -1,12 +1,12 @@
import "server-only";
import { ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getResponse, getResponseCacheTag } from "./service";
import { unstable_cache } from "next/cache";
import { getSurvey } from "../services/survey";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getSurvey } from "../survey/service";
import { validateInputs } from "../utils/validate";
import { getResponse, getResponseCacheTag } from "./service";
export const canUserAccessResponse = async (userId: string, responseId: string): Promise<boolean> =>
await unstable_cache(

View File

@@ -1,15 +1,24 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { z } from "zod";
import { Prisma, EnvironmentType } from "@prisma/client";
import type {
TEnvironment,
TEnvironmentCreateInput,
TEnvironmentUpdateInput,
} from "@formbricks/types/v1/environment";
import {
ZEnvironment,
ZEnvironmentCreateInput,
ZEnvironmentUpdateInput,
ZId,
} from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/v1/errors";
import type { TEnvironment, TEnvironmentId, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
import { populateEnvironment } from "../utils/createDemoProductHelpers";
import { ZEnvironment, ZEnvironmentUpdateInput, ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { unstable_cache, revalidateTag } from "next/cache";
import { EventType, Prisma } from "@prisma/client";
import { revalidateTag, unstable_cache } from "next/cache";
import "server-only";
import { z } from "zod";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { validateInputs } from "../utils/validate";
export const getEnvironmentCacheTag = (environmentId: string) => `environments-${environmentId}`;
export const getEnvironmentsCacheTag = (productId: string) => `products-${productId}-environments`;
@@ -125,86 +134,84 @@ export const updateEnvironment = async (
}
};
export const getEnvironmentByUser = async (user: any): Promise<TEnvironment | TEnvironmentId | null> => {
const firstMembership = await prisma.membership.findFirst({
where: {
userId: user.id,
},
select: {
teamId: true,
},
});
if (!firstMembership) {
// create a new team and return environment
const membership = await prisma.membership.create({
data: {
accepted: true,
role: "owner",
user: { connect: { id: user.id } },
team: {
create: {
name: `${user.name}'s Team`,
products: {
create: {
name: "My Product",
environments: {
create: [
{
type: EnvironmentType.production,
...populateEnvironment,
},
{
type: EnvironmentType.development,
...populateEnvironment,
},
],
},
},
},
},
},
},
include: {
team: {
include: {
products: {
include: {
environments: true,
export const getFirstEnvironmentByUserId = async (userId: string): Promise<TEnvironment | null> => {
validateInputs([userId, ZId]);
let environmentPrisma;
try {
environmentPrisma = await prisma.environment.findFirst({
where: {
type: "production",
product: {
team: {
memberships: {
some: {
userId,
},
},
},
},
},
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError("Database operation failed");
}
const environment = membership.team.products[0].environments[0];
throw error;
}
try {
const environment = ZEnvironment.parse(environmentPrisma);
return environment;
} catch (error) {
if (error instanceof z.ZodError) {
console.error(JSON.stringify(error.errors, null, 2));
}
throw new ValidationError("Data validation of environment failed");
}
const firstProduct = await prisma.product.findFirst({
where: {
teamId: firstMembership.teamId,
},
select: {
id: true,
},
});
if (firstProduct === null) {
return null;
}
const firstEnvironment = await prisma.environment.findFirst({
where: {
productId: firstProduct.id,
type: "production",
},
select: {
id: true,
},
});
if (firstEnvironment === null) {
return null;
}
return firstEnvironment;
};
export const createEnvironment = async (
productId: string,
environmentInput: Partial<TEnvironmentCreateInput>
): Promise<TEnvironment> => {
validateInputs([productId, ZId], [environmentInput, ZEnvironmentCreateInput]);
return await prisma.environment.create({
data: {
type: environmentInput.type || "development",
product: { connect: { id: productId } },
widgetSetupCompleted: environmentInput.widgetSetupCompleted || false,
eventClasses: {
create: populateEnvironment.eventClasses,
},
attributeClasses: {
create: populateEnvironment.attributeClasses,
},
},
});
};
export const populateEnvironment = {
eventClasses: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: EventType.automatic,
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: EventType.automatic,
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: EventType.automatic,
},
],
attributeClasses: [
{ name: "userId", description: "The internal ID of the person", type: EventType.automatic },
{ name: "email", description: "The email of the person", type: EventType.automatic },
],
};

View File

@@ -62,6 +62,26 @@ export const getMembershipsByUserId = cache(async (userId: string): Promise<TMem
return memberships;
});
export const createMembership = async (
teamId: string,
userId: string,
data: Partial<TMembership>
): Promise<TMembership> => {
try {
const membership = await prisma.membership.create({
data: {
userId,
teamId,
accepted: data.accepted,
role: data.role as TMembership["role"],
},
});
return membership;
} catch (error) {
throw error;
}
};
export const updateMembership = async (
userId: string,
teamId: string,

View File

@@ -9,11 +9,9 @@ import { Prisma } from "@prisma/client";
import { revalidateTag, unstable_cache } from "next/cache";
import { cache } from "react";
import { z } from "zod";
import { validateInputs } from "../utils/validate";
import { EnvironmentType } from "@prisma/client";
import { EventType } from "@prisma/client";
import { getEnvironmentCacheTag, getEnvironmentsCacheTag } from "./environment";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { validateInputs } from "../utils/validate";
import { createEnvironment, getEnvironmentCacheTag, getEnvironmentsCacheTag } from "./environment";
export const getProductsCacheTag = (teamId: string): string => `teams-${teamId}-products`;
const getProductCacheTag = (environmentId: string): string => `environments-${environmentId}-product`;
@@ -35,34 +33,6 @@ const selectProduct = {
environments: true,
};
const populateEnvironment = {
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: EventType.automatic,
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: EventType.automatic,
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: EventType.automatic,
},
],
},
attributeClasses: {
create: [
{ name: "userId", description: "The internal ID of the person", type: EventType.automatic },
{ name: "email", description: "The email of the person", type: EventType.automatic },
],
},
};
export const getProducts = async (teamId: string): Promise<TProduct[]> =>
unstable_cache(
async () => {
@@ -135,6 +105,7 @@ export const updateProduct = async (
inputProduct: Partial<TProductUpdateInput>
): Promise<TProduct> => {
validateInputs([productId, ZId], [inputProduct, ZProductUpdateInput.partial()]);
const { environments, ...data } = inputProduct;
let updatedProduct;
try {
updatedProduct = await prisma.product.update({
@@ -142,7 +113,10 @@ export const updateProduct = async (
id: productId,
},
data: {
...inputProduct,
...data,
environments: {
connect: environments?.map((environment) => ({ id: environment.id })) ?? [],
},
},
select: selectProduct,
});
@@ -210,43 +184,35 @@ export const deleteProduct = cache(async (productId: string): Promise<TProduct>
return product;
});
export const createProduct = async (environmentId: string, productName: string): Promise<TProduct> => {
const environment = await prisma.environment.findUnique({
where: { id: environmentId },
select: {
product: {
select: {
teamId: true,
},
},
},
});
if (!environment) {
throw new Error("Invalid environment");
export const createProduct = async (
teamId: string,
productInput: Partial<TProductUpdateInput>
): Promise<TProduct> => {
if (!productInput.name) {
throw new ValidationError("Product Name is required");
}
const { environments, ...data } = productInput;
const newProduct = await prisma.product.create({
let product = await prisma.product.create({
data: {
name: productName,
team: {
connect: { id: environment.product.teamId },
},
environments: {
create: [
{
type: EnvironmentType.production,
...populateEnvironment,
},
{
type: EnvironmentType.development,
...populateEnvironment,
},
],
},
...data,
name: productInput.name,
teamId,
},
select: selectProduct,
});
return newProduct;
const devEnvironment = await createEnvironment(product.id, {
type: "development",
});
const prodEnvironment = await createEnvironment(product.id, {
type: "production",
});
product = await updateProduct(product.id, {
environments: [devEnvironment, prodEnvironment],
});
return product;
};

View File

@@ -4,7 +4,12 @@ import { prisma } from "@formbricks/database";
import { ZId } from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { TMembership, TMembershipRole, ZMembershipRole } from "@formbricks/types/v1/memberships";
import { TProfile, TProfileUpdateInput, ZProfileUpdateInput } from "@formbricks/types/v1/profile";
import {
TProfile,
TProfileCreateInput,
TProfileUpdateInput,
ZProfileUpdateInput,
} from "@formbricks/types/v1/profile";
import { MembershipRole, Prisma } from "@prisma/client";
import { unstable_cache, revalidateTag } from "next/cache";
import { validateInputs } from "../utils/validate";
@@ -149,6 +154,19 @@ const deleteUser = async (userId: string): Promise<TProfile> => {
return profile;
};
export const createProfile = async (data: TProfileCreateInput): Promise<TProfile> => {
validateInputs([data, ZProfileUpdateInput]);
const profile = await prisma.user.create({
data: data,
select: responseSelection,
});
revalidateTag(getProfileByEmailCacheTag(profile.email));
revalidateTag(getProfileCacheTag(profile.id));
return profile;
};
// function to delete a user's profile including teams
export const deleteProfile = async (userId: string): Promise<void> => {
validateInputs([userId, ZId]);

View File

@@ -107,7 +107,20 @@ export const getTeamByEnvironmentId = async (environmentId: string): Promise<TTe
}
)();
export const updateTeam = async (teamId: string, data: TTeamUpdateInput): Promise<TTeam> => {
export const createTeam = async (teamInput: TTeamUpdateInput): Promise<TTeam> => {
try {
const team = await prisma.team.create({
data: teamInput,
select,
});
return team;
} catch (error) {
throw error;
}
};
export const updateTeam = async (teamId: string, data: Partial<TTeamUpdateInput>): Promise<TTeam> => {
try {
const updatedTeam = await prisma.team.update({
where: {

View File

@@ -0,0 +1,24 @@
import { ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getSurvey, getSurveyCacheTag } from "./service";
import { unstable_cache } from "next/cache";
export const canUserAccessSurvey = async (userId: string, surveyId: string): Promise<boolean> =>
await unstable_cache(
async () => {
validateInputs([surveyId, ZId], [userId, ZId]);
if (!userId) return false;
const survey = await getSurvey(surveyId);
if (!survey) throw new Error("Survey not found");
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, survey.environmentId);
if (!hasAccessToEnvironment) return false;
return true;
},
[`users-${userId}-surveys-${surveyId}`],
{ revalidate: 30 * 60, tags: [getSurveyCacheTag(surveyId)] }
)(); // 30 minutes

View File

@@ -15,22 +15,15 @@ import { revalidateTag, unstable_cache } from "next/cache";
import { z } from "zod";
import { captureTelemetry } from "../telemetry";
import { validateInputs } from "../utils/validate";
import { getDisplaysCacheTag } from "./displays";
import { getDisplaysCacheTag } from "../services/displays";
import { getResponsesCacheTag } from "../response/service";
// surveys cache key and tags
const getSurveysCacheKey = (environmentId: string): string => `environments-${environmentId}-surveys`;
const getSurveysCacheTag = (environmentId: string): string => `environments-${environmentId}-surveys`;
// survey cache key and tags
export const getSurveyCacheKey = (surveyId: string): string => `surveys-${surveyId}`;
export const getSurveyCacheTag = (surveyId: string): string => `surveys-${surveyId}`;
// survey with analytics cache key
const getSurveysWithAnalyticsCacheKey = (environmentId: string): string =>
`environments-${environmentId}-surveysWithAnalytics`;
const getSurveyWithAnalyticsCacheKey = (surveyId: string): string => `surveyWithAnalytics-${surveyId}`;
export const selectSurvey = {
id: true,
createdAt: true,
@@ -145,7 +138,7 @@ export const getSurveyWithAnalytics = async (surveyId: string): Promise<TSurveyW
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveyWithAnalyticsCacheKey(surveyId)],
[`surveyWithAnalytics-${surveyId}`],
{
tags: [getSurveyCacheTag(surveyId), getDisplaysCacheTag(surveyId), getResponsesCacheTag(surveyId)],
revalidate: 60 * 30,
@@ -204,7 +197,7 @@ export const getSurvey = async (surveyId: string): Promise<TSurvey | null> => {
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveyCacheKey(surveyId)],
[`surveys-${surveyId}`],
{
tags: [getSurveyCacheTag(surveyId)],
revalidate: 60 * 30,
@@ -329,7 +322,7 @@ export const getSurveys = async (environmentId: string): Promise<TSurvey[]> => {
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveysCacheKey(environmentId)],
[`environments-${environmentId}-surveys`],
{
tags: [getSurveysCacheTag(environmentId)],
revalidate: 60 * 30,
@@ -393,7 +386,7 @@ export const getSurveysWithAnalytics = async (environmentId: string): Promise<TS
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveysWithAnalyticsCacheKey(environmentId)],
[`environments-${environmentId}-surveysWithAnalytics`],
{
tags: [getSurveysCacheTag(environmentId)], // TODO: add tags for displays and responses
}

View File

@@ -44,4 +44,12 @@ export const ZActionClassInput = z.object({
type: z.enum(["code", "noCode"]),
});
export const ZActionClassAutomaticInput = z.object({
name: z.string(),
description: z.string().optional(),
type: z.enum(["automatic"]),
});
export type TActionClassAutomaticInput = z.infer<typeof ZActionClassAutomaticInput>;
export type TActionClassInput = z.infer<typeof ZActionClassInput>;

View File

@@ -22,12 +22,20 @@ export const ZAttributeClassInput = z.object({
environmentId: z.string(),
});
export const ZAttributeClassAutomaticInput = z.object({
name: z.string(),
description: z.string(),
type: z.enum(["automatic"]),
});
export const ZAttributeClassUpdateInput = z.object({
name: z.string(),
description: z.string().optional(),
archived: z.boolean().optional(),
});
export type TAttributeClassAutomaticInput = z.infer<typeof ZAttributeClassAutomaticInput>;
export type TAttributeClassUpdateInput = z.infer<typeof ZAttributeClassUpdateInput>;
export type TAttributeClassInput = z.infer<typeof ZAttributeClassInput>;

View File

@@ -25,4 +25,11 @@ export const ZEnvironmentUpdateInput = z.object({
export const ZId = z.string().cuid2();
export const ZEnvironmentCreateInput = z.object({
type: z.enum(["development", "production"]).optional(),
widgetSetupCompleted: z.boolean().optional(),
});
export type TEnvironmentCreateInput = z.infer<typeof ZEnvironmentCreateInput>;
export type TEnvironmentUpdateInput = z.infer<typeof ZEnvironmentUpdateInput>;

View File

@@ -11,7 +11,7 @@ export const ZProduct = z.object({
highlightBorderColor: z
.string()
.regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)
.nullish(),
.nullable(),
recontactDays: z.number().int(),
formbricksSignature: z.boolean(),
placement: z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]),
@@ -26,7 +26,6 @@ export const ZProductUpdateInput = ZProduct.omit({
id: true,
createdAt: true,
updatedAt: true,
environments: true,
});
export type TProductUpdateInput = z.infer<typeof ZProductUpdateInput>;

View File

@@ -21,11 +21,21 @@ export const ZProfile = z.object({
export type TProfile = z.infer<typeof ZProfile>;
export const ZProfileUpdateInput = z.object({
name: z.string().nullable(),
email: z.string(),
onboardingCompleted: z.boolean(),
role: ZRole.nullable(),
objective: ZObjective.nullable(),
name: z.string().nullish(),
email: z.string().optional(),
onboardingCompleted: z.boolean().optional(),
role: ZRole.optional(),
objective: ZObjective.optional(),
});
export type TProfileUpdateInput = z.infer<typeof ZProfileUpdateInput>;
export const ZProfileCreateInput = z.object({
name: z.string().optional(),
email: z.string(),
onboardingCompleted: z.boolean().optional(),
role: ZRole.optional(),
objective: ZObjective.optional(),
});
export type TProfileCreateInput = z.infer<typeof ZProfileCreateInput>;

View File

@@ -1,5 +1,4 @@
import { z } from "zod";
import { Prisma } from "@prisma/client";
export const ZTeam = z.object({
id: z.string().cuid2(),
@@ -10,6 +9,12 @@ export const ZTeam = z.object({
stripeCustomerId: z.string().nullable(),
});
export type TTeamUpdateInput = Prisma.TeamUpdateInput;
export const ZTeamUpdateInput = z.object({
name: z.string(),
plan: z.enum(["free", "pro"]).optional(),
stripeCustomerId: z.string().nullish(),
});
export type TTeamUpdateInput = z.infer<typeof ZTeamUpdateInput>;
export type TTeam = z.infer<typeof ZTeam>;