Merge branch 'main' into fix/multi-select-optional

This commit is contained in:
Johannes
2023-10-02 17:08:53 +05:45
committed by GitHub
20 changed files with 228 additions and 117 deletions

View File

@@ -9,11 +9,14 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { testURLmatch } from "./testURLmatch";
import { deleteActionClass, updateActionClass } from "@formbricks/lib/services/actionClass";
import { TActionClassInput, TActionClassNoCodeConfig } from "@formbricks/types/v1/actionClasses";
import { CssSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/CssSelector";
import { PageUrlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/PageUrlSelector";
import { InnerHtmlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/InnerHtmlSelector";
import {
deleteActionClassAction,
updateActionClassAction,
} from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions";
interface ActionSettingsTabProps {
environmentId: string;
@@ -77,10 +80,11 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen
const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as NoCodeConfig);
const updatedData: TActionClassInput = {
...data,
environmentId,
noCodeConfig: filteredNoCodeConfig,
type: "noCode",
} as TActionClassInput;
await updateActionClass(environmentId, actionClass.id, updatedData);
await updateActionClassAction(environmentId, actionClass.id, updatedData);
setOpen(false);
router.refresh();
toast.success("Action updated successfully");
@@ -94,7 +98,7 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen
const handleDeleteAction = async () => {
try {
setIsDeletingAction(true);
await deleteActionClass(environmentId, actionClass.id);
await deleteActionClassAction(environmentId, actionClass.id);
router.refresh();
toast.success("Action deleted successfully");
setOpen(false);

View File

@@ -7,7 +7,6 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { testURLmatch } from "./testURLmatch";
import { createActionClass } from "@formbricks/lib/services/actionClass";
import {
TActionClassInput,
TActionClassNoCodeConfig,
@@ -16,6 +15,7 @@ import {
import { CssSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/CssSelector";
import { PageUrlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/PageUrlSelector";
import { InnerHtmlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/InnerHtmlSelector";
import { createActionClassAction } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions";
interface AddNoCodeActionModalProps {
environmentId: string;
@@ -84,7 +84,7 @@ export default function AddNoCodeActionModal({
type: "noCode",
} as TActionClassInput;
const newActionClass: TActionClass = await createActionClass(environmentId, updatedData);
const newActionClass: TActionClass = await createActionClassAction(updatedData);
if (setActionClassArray) {
setActionClassArray((prevActionClassArray: TActionClass[]) => [
...prevActionClassArray,

View File

@@ -1,27 +1,93 @@
"use server";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createActionClass, deleteActionClass, updateActionClass } from "@formbricks/lib/actionClass/service";
import { canUserAccessActionClass } from "@formbricks/lib/actionClass/auth";
import { getServerSession } from "next-auth";
import { TActionClassInput } from "@formbricks/types/v1/actionClasses";
import {
getActionCountInLast24Hours,
getActionCountInLast7Days,
getActionCountInLastHour,
} from "@formbricks/lib/services/actions";
import { getSurveysByActionClassId } from "@formbricks/lib/services/survey";
import { AuthorizationError } from "@formbricks/types/v1/errors";
export async function deleteActionClassAction(environmentId, actionClassId: string) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
await deleteActionClass(environmentId, actionClassId);
}
export async function updateActionClassAction(
environmentId: string,
actionClassId: string,
updatedAction: Partial<TActionClassInput>
) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await updateActionClass(environmentId, actionClassId, updatedAction);
}
export async function createActionClassAction(action: TActionClassInput) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, action.environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await createActionClass(action.environmentId, action);
}
export const getActionCountInLastHourAction = async (actionClassId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await getActionCountInLastHour(actionClassId);
};
export const getActionCountInLast24HoursAction = async (actionClassId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await getActionCountInLast24Hours(actionClassId);
};
export const getActionCountInLast7DaysAction = async (actionClassId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await getActionCountInLast7Days(actionClassId);
};
export const GetActiveInactiveSurveysAction = async (
actionClassId: string
): Promise<{ activeSurveys: string[]; inactiveSurveys: string[] }> => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const surveys = await getSurveysByActionClassId(actionClassId);
const response = {
activeSurveys: surveys.filter((s) => s.status === "inProgress").map((survey) => survey.name),

View File

@@ -4,7 +4,7 @@ import ActionClassesTable from "@/app/(app)/environments/[environmentId]/(action
import ActionClassDataRow from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionRowData";
import ActionTableHeading from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionTableHeading";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getActionClasses } from "@formbricks/lib/services/actionClass";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { Metadata } from "next";
export const metadata: Metadata = {

View File

@@ -11,22 +11,18 @@ import { AuthorizationError } from "@formbricks/types/v1/errors";
export async function deleteApiKeyAction(id: string) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessApiKey(session.user.id, id);
if (isAuthorized) {
return await deleteApiKey(id);
} else {
throw new AuthorizationError("Not authorized");
}
const isAuthorized = await canUserAccessApiKey(session.user.id, id);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await deleteApiKey(id);
}
export async function createApiKeyAction(environmentId: string, apiKeyData: TApiKeyCreateInput) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (isAuthorized) {
return await createApiKey(environmentId, apiKeyData);
} else {
throw new AuthorizationError("Not authorized");
}
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await createApiKey(environmentId, apiKeyData);
}

View File

@@ -4,6 +4,7 @@ import { TSurveyCTAQuestion } from "@formbricks/types/v1/surveys";
import { ProgressBar } from "@formbricks/ui";
import { InboxStackIcon } from "@heroicons/react/24/solid";
import { useMemo } from "react";
import { questionTypes } from "@/lib/questions";
interface CTASummaryProps {
questionSummary: QuestionSummary<TSurveyCTAQuestion>;
@@ -15,6 +16,8 @@ interface ChoiceResult {
}
export default function CTASummary({ questionSummary }: CTASummaryProps) {
const questionTypeInfo = questionTypes.find((type) => type.id === questionSummary.question.type);
const ctr: ChoiceResult = useMemo(() => {
const clickedAbs = questionSummary.responses.filter((response) => response.value === "clicked").length;
const count = questionSummary.responses.length;
@@ -31,7 +34,10 @@ export default function CTASummary({ questionSummary }: CTASummaryProps) {
<Headline headline={questionSummary.question.headline} required={questionSummary.question.required} />
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
<div className="rounded-lg bg-slate-100 p-2 ">Call-to-Action</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2 ">
{questionTypeInfo && <questionTypeInfo.icon className="mr-2 h-4 w-4 " />}
{questionTypeInfo ? questionTypeInfo.label : "Unknown Question Type"}
</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " />
{ctr.count} responses

View File

@@ -3,6 +3,7 @@ import { ProgressBar } from "@formbricks/ui";
import { InboxStackIcon } from "@heroicons/react/24/solid";
import { useMemo } from "react";
import { TSurveyConsentQuestion } from "@formbricks/types/v1/surveys";
import { questionTypes } from "@/lib/questions";
import Headline from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/Headline";
interface ConsentSummaryProps {
@@ -18,6 +19,8 @@ interface ChoiceResult {
}
export default function ConsentSummary({ questionSummary }: ConsentSummaryProps) {
const questionTypeInfo = questionTypes.find((type) => type.id === questionSummary.question.type);
const ctr: ChoiceResult = useMemo(() => {
const total = questionSummary.responses.length;
const clickedAbs = questionSummary.responses.filter((response) => response.value !== "dismissed").length;
@@ -38,7 +41,10 @@ export default function ConsentSummary({ questionSummary }: ConsentSummaryProps)
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
<Headline headline={questionSummary.question.headline} required={questionSummary.question.required} />
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
<div className="rounded-lg bg-slate-100 p-2">Consent</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
{questionTypeInfo && <questionTypeInfo.icon className="mr-2 h-4 w-4 " />}
{questionTypeInfo ? questionTypeInfo.label : "Unknown Question Type"}
</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " />
{ctr.count} responses

View File

@@ -9,6 +9,7 @@ import {
TSurveyMultipleChoiceMultiQuestion,
TSurveyMultipleChoiceSingleQuestion,
} from "@formbricks/types/v1/surveys";
import { questionTypes } from "@/lib/questions";
import Headline from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/Headline";
interface MultipleChoiceSummaryProps {
@@ -39,6 +40,8 @@ export default function MultipleChoiceSummary({
}: MultipleChoiceSummaryProps) {
const isSingleChoice = questionSummary.question.type === QuestionType.MultipleChoiceSingle;
const questionTypeInfo = questionTypes.find((type) => type.id === questionSummary.question.type);
const results: ChoiceResult[] = useMemo(() => {
if (!("choices" in questionSummary.question)) return [];
@@ -129,10 +132,9 @@ export default function MultipleChoiceSummary({
<Headline headline={questionSummary.question.headline} required={questionSummary.question.required} />
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
<div className="rounded-lg bg-slate-100 p-2">
{isSingleChoice
? "Multiple-Choice Single Select Question"
: "Multiple-Choice Multi Select Question"}
<div className="flex items-center rounded-lg bg-slate-100 p-2">
{questionTypeInfo && <questionTypeInfo.icon className="mr-2 h-4 w-4 " />}
Multiple-Choice {questionTypeInfo ? questionTypeInfo.label : "Unknown Question Type"} Question
</div>
<div className="flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " />

View File

@@ -4,6 +4,7 @@ import { TSurveyNPSQuestion } from "@formbricks/types/v1/surveys";
import { HalfCircle, ProgressBar } from "@formbricks/ui";
import { InboxStackIcon } from "@heroicons/react/24/solid";
import { useMemo } from "react";
import { questionTypes } from "@/lib/questions";
interface NPSSummaryProps {
questionSummary: QuestionSummary<TSurveyNPSQuestion>;
@@ -29,6 +30,8 @@ export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
return result || 0;
};
const questionTypeInfo = questionTypes.find((type) => type.id === questionSummary.question.type);
const result: Result = useMemo(() => {
let data = {
promoters: 0,
@@ -79,7 +82,10 @@ export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
<Headline headline={questionSummary.question.headline} required={questionSummary.question.required} />
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
<div className="rounded-lg bg-slate-100 p-2">Net Promoter Score (NPS)</div>
<div className="flex items-center rounded-lg bg-slate-100 p-2">
{questionTypeInfo && <questionTypeInfo.icon className="mr-2 h-4 w-4 " />}
{questionTypeInfo ? questionTypeInfo.label : "Unknown Question Type"}
</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " />
{result.total} responses

View File

@@ -6,6 +6,7 @@ import { TSurveyOpenTextQuestion } from "@formbricks/types/v1/surveys";
import { PersonAvatar } from "@formbricks/ui";
import { InboxStackIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
import { questionTypes } from "@/lib/questions";
interface OpenTextSummaryProps {
questionSummary: QuestionSummary<TSurveyOpenTextQuestion>;
@@ -17,13 +18,18 @@ function findEmail(person) {
}
export default function OpenTextSummary({ questionSummary, environmentId }: OpenTextSummaryProps) {
const questionTypeInfo = questionTypes.find((type) => type.id === questionSummary.question.type);
return (
<div className="rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
<Headline headline={questionSummary.question.headline} required={questionSummary.question.required} />
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
<div className="rounded-lg bg-slate-100 p-2 ">Open Text Question</div>
<div className="flex items-center rounded-lg bg-slate-100 p-2 ">
{questionTypeInfo && <questionTypeInfo.icon className="mr-2 h-4 w-4 " />}
{questionTypeInfo ? questionTypeInfo.label : "Unknown Question Type"} Question
</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4" />
{questionSummary.responses.length} Responses

View File

@@ -5,6 +5,7 @@ import { useMemo } from "react";
import { QuestionType } from "@formbricks/types/questions";
import { TSurveyRatingQuestion } from "@formbricks/types/v1/surveys";
import { RatingResponse } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/RatingResponse";
import { questionTypes } from "@/lib/questions";
import Headline from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/Headline";
interface RatingSummaryProps {
@@ -18,6 +19,8 @@ interface ChoiceResult {
}
export default function RatingSummary({ questionSummary }: RatingSummaryProps) {
const questionTypeInfo = questionTypes.find((type) => type.id === questionSummary.question.type);
const results: ChoiceResult[] = useMemo(() => {
if (questionSummary.question.type !== QuestionType.Rating) return [];
// build a dictionary of choices
@@ -81,7 +84,10 @@ export default function RatingSummary({ questionSummary }: RatingSummaryProps) {
<Headline headline={questionSummary.question.headline} required={questionSummary.question.required} />
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
<div className="rounded-lg bg-slate-100 p-2">Rating Question</div>
<div className="flex items-center rounded-lg bg-slate-100 p-2">
{questionTypeInfo && <questionTypeInfo.icon className="mr-2 h-4 w-4 " />}
{questionTypeInfo ? questionTypeInfo.label : "Unknown Question Type"} Question
</div>
<div className="flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " />
{totalResponses} responses

View File

@@ -5,7 +5,7 @@ import SurveyEditor from "./SurveyEditor";
import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getActionClasses } from "@formbricks/lib/services/actionClass";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { getAttributeClasses } from "@formbricks/lib/services/attributeClass";
import { ErrorComponent } from "@formbricks/ui";

View File

@@ -1,6 +1,6 @@
import { getSurveysCached } from "@/app/api/v1/js/surveys";
import { MAU_LIMIT } from "@formbricks/lib/constants";
import { getActionClassesCached } from "@formbricks/lib/services/actionClass";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { createPerson, getMonthlyActivePeopleCount, getPersonCached } from "@formbricks/lib/services/person";
import { getProductByEnvironmentIdCached } from "@formbricks/lib/services/product";
@@ -101,7 +101,7 @@ export const getUpdatedState = async (
// get/create rest of the state
const [surveys, noCodeActionClasses, product] = await Promise.all([
getSurveysCached(environmentId, person),
getActionClassesCached(environmentId),
getActionClasses(environmentId),
getProductByEnvironmentIdCached(environmentId),
]);

View File

@@ -1,6 +1,6 @@
import { responses } from "@/lib/api/response";
import { NextResponse } from "next/server";
import { deleteActionClass, getActionClass, updateActionClass } from "@formbricks/lib/services/actionClass";
import { deleteActionClass, getActionClass, updateActionClass } from "@formbricks/lib/actionClass/service";
import { TActionClass, ZActionClassInput } from "@formbricks/types/v1/actionClasses";
import { authenticateRequest } from "@/app/api/v1/auth";
import { transformErrorToDetails } from "@/lib/api/validator";

View File

@@ -3,7 +3,7 @@ import { DatabaseError } from "@formbricks/types/v1/errors";
import { authenticateRequest } from "@/app/api/v1/auth";
import { NextResponse } from "next/server";
import { TActionClass, ZActionClassInput } from "@formbricks/types/v1/actionClasses";
import { createActionClass, getActionClasses } from "@formbricks/lib/services/actionClass";
import { createActionClass, getActionClasses } from "@formbricks/lib/actionClass/service";
import { transformErrorToDetails } from "@/lib/api/validator";
export async function GET(request: Request) {

View File

@@ -0,0 +1,24 @@
import { ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getActionClass } from "./service";
import { unstable_cache } from "next/cache";
export const canUserAccessActionClass = async (userId: string, actionClassId: string): Promise<boolean> =>
await unstable_cache(
async () => {
validateInputs([userId, ZId], [actionClassId, ZId]);
if (!userId) return false;
const actionClass = await getActionClass(actionClassId);
if (!actionClass) return false;
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, actionClass.environmentId);
if (!hasAccessToEnvironment) return false;
return true;
},
[`users-${userId}-actionClasses-${actionClassId}`],
{ revalidate: 30 * 60, tags: [`actionClasses-${actionClassId}`] }
)(); // 30 minutes

View File

@@ -6,22 +6,15 @@ import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/
import { ZId } from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { revalidateTag, unstable_cache } from "next/cache";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
const halfHourInSeconds = 60 * 30;
export const getActionClassCacheTag = (name: string, environmentId: string): string =>
`environments-${environmentId}-actionClass-${name}`;
const getActionClassCacheKey = (name: string, environmentId: string): string[] => [
getActionClassCacheTag(name, environmentId),
];
const getActionClassesCacheTag = (environmentId: string): string =>
`environments-${environmentId}-actionClasses`;
const getActionClassesCacheKey = (environmentId: string): string[] => [
getActionClassesCacheTag(environmentId),
];
const select = {
id: true,
@@ -34,33 +27,29 @@ const select = {
environmentId: true,
};
export const getActionClasses = cache(async (environmentId: string): Promise<TActionClass[]> => {
validateInputs([environmentId, ZId]);
try {
let actionClasses = await prisma.eventClass.findMany({
where: {
environmentId: environmentId,
},
select,
orderBy: {
createdAt: "asc",
},
});
return actionClasses;
} catch (error) {
throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`);
}
});
export const getActionClassesCached = (environmentId: string) =>
export const getActionClasses = (environmentId: string): Promise<TActionClass[]> =>
unstable_cache(
async () => {
return await getActionClasses(environmentId);
validateInputs([environmentId, ZId]);
try {
let actionClasses = await prisma.eventClass.findMany({
where: {
environmentId: environmentId,
},
select,
orderBy: {
createdAt: "asc",
},
});
return actionClasses;
} catch (error) {
throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`);
}
},
getActionClassesCacheKey(environmentId),
[`environments-${environmentId}-actionClasses`],
{
tags: getActionClassesCacheKey(environmentId),
tags: [getActionClassesCacheTag(environmentId)],
revalidate: halfHourInSeconds,
}
)();
@@ -96,7 +85,7 @@ export const deleteActionClass = async (
if (result === null) throw new ResourceNotFoundError("Action", actionClassId);
// revalidate cache
revalidateTag(getActionClassesCacheTag(environmentId));
revalidateTag(getActionClassesCacheTag(result.environmentId));
return result;
} catch (error) {
@@ -157,8 +146,8 @@ export const updateActionClass = async (
});
// revalidate cache
revalidateTag(getActionClassCacheTag(result.name, environmentId));
revalidateTag(getActionClassesCacheTag(environmentId));
revalidateTag(getActionClassCacheTag(result.name, result.environmentId));
revalidateTag(getActionClassesCacheTag(result.environmentId));
return result;
} catch (error) {
@@ -176,9 +165,9 @@ export const getActionClassCached = async (name: string, environmentId: string)
},
});
},
getActionClassCacheKey(name, environmentId),
[`environments-${environmentId}-actionClasses-${name}`],
{
tags: getActionClassCacheKey(name, environmentId),
tags: [getActionClassesCacheTag(environmentId)],
revalidate: halfHourInSeconds,
}
)();

View File

@@ -2,8 +2,8 @@ import { hasUserEnvironmentAccess } from "../environment/auth";
import { getApiKey } from "./service";
import { unstable_cache } from "next/cache";
export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise<boolean> => {
return await unstable_cache(
export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise<boolean> =>
await unstable_cache(
async () => {
if (!userId) return false;
@@ -19,4 +19,3 @@ export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Pro
[`users-${userId}-apiKeys-${apiKeyId}`],
{ revalidate: 30 * 60, tags: [`apiKeys-${apiKeyId}`] }
)(); // 30 minutes
};

View File

@@ -11,7 +11,7 @@ import { validateInputs } from "../utils/validate";
import { TJsActionInput } from "@formbricks/types/v1/js";
import { revalidateTag } from "next/cache";
import { EventType } from "@prisma/client";
import { getActionClassCacheTag, getActionClassCached } from "../services/actionClass";
import { getActionClassCacheTag, getActionClassCached } from "../actionClass/service";
import { getSessionCached } from "../services/session";
export const getActionsByEnvironmentId = cache(

93
pnpm-lock.yaml generated
View File

@@ -24,7 +24,7 @@ importers:
version: 3.12.7
turbo:
specifier: latest
version: 1.10.14
version: 1.10.13
apps/demo:
dependencies:
@@ -1147,7 +1147,7 @@ packages:
'@babel/helper-plugin-utils': 7.22.5
debug: 4.3.4
lodash.debounce: 4.0.8
resolve: 1.22.2
resolve: 1.22.6
transitivePeerDependencies:
- supports-color
dev: true
@@ -13159,11 +13159,6 @@ packages:
rgba-regex: 1.0.0
dev: true
/is-core-module@2.11.0:
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
dependencies:
has: 1.0.3
/is-core-module@2.13.0:
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==}
dependencies:
@@ -14718,7 +14713,7 @@ packages:
is-plain-object: 2.0.4
object.map: 1.0.1
rechoir: 0.6.2
resolve: 1.22.2
resolve: 1.22.6
transitivePeerDependencies:
- supports-color
dev: true
@@ -15141,7 +15136,7 @@ packages:
dependencies:
findup-sync: 2.0.0
micromatch: 3.1.10
resolve: 1.22.2
resolve: 1.22.6
stack-trace: 0.0.10
transitivePeerDependencies:
- supports-color
@@ -16557,7 +16552,7 @@ packages:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
hosted-git-info: 2.8.9
resolve: 1.22.2
resolve: 1.22.6
semver: 5.7.2
validate-npm-package-license: 3.0.4
dev: true
@@ -17621,7 +17616,7 @@ packages:
postcss: 8.4.27
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.2
resolve: 1.22.6
/postcss-js@4.0.1(postcss@8.4.27):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
@@ -19917,7 +19912,7 @@ packages:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
dependencies:
is-core-module: 2.11.0
is-core-module: 2.13.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
@@ -19926,7 +19921,7 @@ packages:
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
hasBin: true
dependencies:
is-core-module: 2.11.0
is-core-module: 2.13.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
@@ -19942,7 +19937,7 @@ packages:
resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==}
hasBin: true
dependencies:
is-core-module: 2.11.0
is-core-module: 2.13.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
@@ -20117,16 +20112,8 @@ packages:
fsevents: 2.3.2
dev: true
/rollup@3.28.1:
resolution: {integrity: sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/rollup@3.5.1:
resolution: {integrity: sha512-hdQWTvPeiAbM6SUkxV70HdGUVxsgsc+CLy5fuh4KdgUBJ0SowXiix8gANgXoG3wEuLwfoJhCT2V+WwxfWq9Ikw==}
/rollup@3.29.4:
resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
@@ -21995,7 +21982,7 @@ packages:
joycon: 3.1.1
postcss-load-config: 4.0.1(postcss@8.4.27)
resolve-from: 5.0.0
rollup: 3.5.1
rollup: 3.29.4
source-map: 0.8.0-beta.0
sucrase: 3.32.0
tree-kill: 1.2.2
@@ -22049,64 +22036,78 @@ packages:
dependencies:
safe-buffer: 5.2.1
/turbo-darwin-64@1.10.14:
resolution: {integrity: sha512-I8RtFk1b9UILAExPdG/XRgGQz95nmXPE7OiGb6ytjtNIR5/UZBS/xVX/7HYpCdmfriKdVwBKhalCoV4oDvAGEg==}
/turbo-darwin-64@1.10.13:
resolution: {integrity: sha512-vmngGfa2dlYvX7UFVncsNDMuT4X2KPyPJ2Jj+xvf5nvQnZR/3IeDEGleGVuMi/hRzdinoxwXqgk9flEmAYp0Xw==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-darwin-arm64@1.10.14:
resolution: {integrity: sha512-KAdUWryJi/XX7OD0alOuOa0aJ5TLyd4DNIYkHPHYcM6/d7YAovYvxRNwmx9iv6Vx6IkzTnLeTiUB8zy69QkG9Q==}
/turbo-darwin-arm64@1.10.13:
resolution: {integrity: sha512-eMoJC+k7gIS4i2qL6rKmrIQGP6Wr9nN4odzzgHFngLTMimok2cGLK3qbJs5O5F/XAtEeRAmuxeRnzQwTl/iuAw==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-linux-64@1.10.14:
resolution: {integrity: sha512-BOBzoREC2u4Vgpap/WDxM6wETVqVMRcM8OZw4hWzqCj2bqbQ6L0wxs1LCLWVrghQf93JBQtIGAdFFLyCSBXjWQ==}
/turbo-linux-64@1.10.13:
resolution: {integrity: sha512-0CyYmnKTs6kcx7+JRH3nPEqCnzWduM0hj8GP/aodhaIkLNSAGAa+RiYZz6C7IXN+xUVh5rrWTnU2f1SkIy7Gdg==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-linux-arm64@1.10.14:
resolution: {integrity: sha512-D8T6XxoTdN5D4V5qE2VZG+/lbZX/89BkAEHzXcsSUTRjrwfMepT3d2z8aT6hxv4yu8EDdooZq/2Bn/vjMI32xw==}
/turbo-linux-arm64@1.10.13:
resolution: {integrity: sha512-0iBKviSGQQlh2OjZgBsGjkPXoxvRIxrrLLbLObwJo3sOjIH0loGmVIimGS5E323soMfi/o+sidjk2wU1kFfD7Q==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-windows-64@1.10.14:
resolution: {integrity: sha512-zKNS3c1w4i6432N0cexZ20r/aIhV62g69opUn82FLVs/zk3Ie0GVkSB6h0rqIvMalCp7enIR87LkPSDGz9K4UA==}
/turbo-windows-64@1.10.13:
resolution: {integrity: sha512-S5XySRfW2AmnTeY1IT+Jdr6Goq7mxWganVFfrmqU+qqq3Om/nr0GkcUX+KTIo9mPrN0D3p5QViBRzulwB5iuUQ==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo-windows-arm64@1.10.14:
resolution: {integrity: sha512-rkBwrTPTxNSOUF7of8eVvvM+BkfkhA2OvpHM94if8tVsU+khrjglilp8MTVPHlyS9byfemPAmFN90oRIPB05BA==}
/turbo-windows-arm64@1.10.13:
resolution: {integrity: sha512-nKol6+CyiExJIuoIc3exUQPIBjP9nIq5SkMJgJuxsot2hkgGrafAg/izVDRDrRduQcXj2s8LdtxJHvvnbI8hEQ==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo@1.10.14:
resolution: {integrity: sha512-hr9wDNYcsee+vLkCDIm8qTtwhJ6+UAMJc3nIY6+PNgUTtXcQgHxCq8BGoL7gbABvNWv76CNbK5qL4Lp9G3ZYRA==}
/turbo@1.10.13:
resolution: {integrity: sha512-vOF5IPytgQPIsgGtT0n2uGZizR2N3kKuPIn4b5p5DdeLoI0BV7uNiydT7eSzdkPRpdXNnO8UwS658VaI4+YSzQ==}
hasBin: true
optionalDependencies:
turbo-darwin-64: 1.10.14
turbo-darwin-arm64: 1.10.14
turbo-linux-64: 1.10.14
turbo-linux-arm64: 1.10.14
turbo-windows-64: 1.10.14
turbo-windows-arm64: 1.10.14
turbo-darwin-64: 1.10.13
turbo-darwin-arm64: 1.10.13
turbo-linux-64: 1.10.13
turbo-linux-arm64: 1.10.13
turbo-windows-64: 1.10.13
turbo-windows-arm64: 1.10.13
dev: true
/tween-functions@1.2.0:
@@ -22980,7 +22981,7 @@ packages:
dependencies:
esbuild: 0.18.10
postcss: 8.4.30
rollup: 3.28.1
rollup: 3.29.4
terser: 5.20.0
optionalDependencies:
fsevents: 2.3.2