From d8b14f5f3566e13ddbc72d830fbf588903430417 Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Sat, 16 Sep 2023 19:05:50 +0530 Subject: [PATCH] chore: add caching to js actions endpoint (#822) * fix: caching in actions endpoint * refactor --------- Co-authored-by: Matthias Nannt --- apps/web/app/api/v1/js/actions/route.ts | 44 ++----------- packages/database/src/client.ts | 4 +- packages/lib/services/actionClass.ts | 28 ++++++++- packages/lib/services/actions.ts | 82 +++++++++++++++++++++++++ packages/lib/services/session.ts | 10 ++- turbo.json | 1 + 6 files changed, 126 insertions(+), 43 deletions(-) diff --git a/apps/web/app/api/v1/js/actions/route.ts b/apps/web/app/api/v1/js/actions/route.ts index a8d5a0af61..883f95260c 100644 --- a/apps/web/app/api/v1/js/actions/route.ts +++ b/apps/web/app/api/v1/js/actions/route.ts @@ -1,8 +1,7 @@ import { responses } from "@/lib/api/response"; import { transformErrorToDetails } from "@/lib/api/validator"; -import { prisma } from "@formbricks/database"; +import { createAction } from "@formbricks/lib/services/actions"; import { ZJsActionInput } from "@formbricks/types/v1/js"; -import { EventType } from "@prisma/client"; import { NextResponse } from "next/server"; export async function OPTIONS(): Promise { @@ -26,42 +25,11 @@ export async function POST(req: Request): Promise { const { environmentId, sessionId, name, properties } = inputValidation.data; - let eventType: EventType = EventType.code; - if (name === "Exit Intent (Desktop)" || name === "50% Scroll") { - eventType = EventType.automatic; - } - - await prisma.event.create({ - data: { - properties, - session: { - connect: { - id: sessionId, - }, - }, - eventClass: { - connectOrCreate: { - where: { - name_environmentId: { - name, - environmentId, - }, - }, - create: { - name, - type: eventType, - environment: { - connect: { - id: environmentId, - }, - }, - }, - }, - }, - }, - select: { - id: true, - }, + createAction({ + environmentId, + sessionId, + name, + properties, }); return responses.successResponse({}, true); diff --git a/packages/database/src/client.ts b/packages/database/src/client.ts index ec9aa79bb9..d77b971450 100644 --- a/packages/database/src/client.ts +++ b/packages/database/src/client.ts @@ -4,7 +4,9 @@ import { withAccelerate } from "@prisma/extension-accelerate"; const prismaClientSingleton = () => { return new PrismaClient({ datasources: { db: { url: process.env.DATABASE_URL } }, - /* log: ["query", "info"], */ + ...(process.env.DEBUG === "1" && { + log: ["query", "info"], + }), }).$extends(withAccelerate()); }; diff --git a/packages/lib/services/actionClass.ts b/packages/lib/services/actionClass.ts index a74185edb9..d3381fcc46 100644 --- a/packages/lib/services/actionClass.ts +++ b/packages/lib/services/actionClass.ts @@ -9,6 +9,14 @@ 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 => + `env-${environmentId}-actionClass-${name}`; +const getActionClassCacheKey = (name: string, environmentId: string): string[] => [ + getActionClassCacheTag(name, environmentId), +]; + const getActionClassesCacheTag = (environmentId: string): string => `env-${environmentId}-actionClasses`; const getActionClassesCacheKey = (environmentId: string): string[] => [ getActionClassesCacheTag(environmentId), @@ -52,7 +60,7 @@ export const getActionClassesCached = (environmentId: string) => getActionClassesCacheKey(environmentId), { tags: getActionClassesCacheKey(environmentId), - revalidate: 30 * 60, // 30 minutes + revalidate: halfHourInSeconds, } )(); @@ -132,6 +140,7 @@ export const updateActionClass = async ( }); // revalidate cache + revalidateTag(getActionClassCacheTag(result.name, environmentId)); revalidateTag(getActionClassesCacheTag(environmentId)); return result; @@ -139,3 +148,20 @@ export const updateActionClass = async ( throw new DatabaseError(`Database error when updating an action for environment ${environmentId}`); } }; + +export const getActionClassCached = async (name: string, environmentId: string) => + unstable_cache( + async () => { + return await prisma.eventClass.findFirst({ + where: { + name, + environmentId, + }, + }); + }, + getActionClassCacheKey(name, environmentId), + { + tags: getActionClassCacheKey(name, environmentId), + revalidate: halfHourInSeconds, + } + )(); diff --git a/packages/lib/services/actions.ts b/packages/lib/services/actions.ts index c254e27cde..b4c5ce28c8 100644 --- a/packages/lib/services/actions.ts +++ b/packages/lib/services/actions.ts @@ -8,6 +8,11 @@ import { ZId } from "@formbricks/types/v1/environment"; import { Prisma } from "@prisma/client"; import { cache } from "react"; 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 { getSessionCached } from "../services/session"; export const getActionsByEnvironmentId = cache( async (environmentId: string, limit?: number): Promise => { @@ -48,3 +53,80 @@ export const getActionsByEnvironmentId = cache( } } ); + +export const createAction = async (data: TJsActionInput) => { + const { environmentId, name, properties, sessionId } = data; + + let eventType: EventType = EventType.code; + if (name === "Exit Intent (Desktop)" || name === "50% Scroll") { + eventType = EventType.automatic; + } + + const session = await getSessionCached(sessionId); + + if (!session) { + throw new Error("Session not found"); + } + + const actionClass = await getActionClassCached(name, environmentId); + + if (actionClass) { + await prisma.event.create({ + data: { + properties, + sessionId: session.id, + eventClassId: actionClass.id, + }, + }); + + return; + } + + // if action class does not exist, create it and then create the action + await prisma.$transaction([ + prisma.eventClass.create({ + data: { + name, + type: eventType, + environmentId, + }, + }), + + prisma.event.create({ + data: { + properties, + session: { + connect: { + id: sessionId, + }, + }, + eventClass: { + connectOrCreate: { + where: { + name_environmentId: { + name, + environmentId, + }, + }, + create: { + name, + type: eventType, + environment: { + connect: { + id: environmentId, + }, + }, + }, + }, + }, + }, + select: { + id: true, + }, + }), + ]); + + // revalidate cache + revalidateTag(sessionId); + revalidateTag(getActionClassCacheTag(name, environmentId)); +}; diff --git a/packages/lib/services/session.ts b/packages/lib/services/session.ts index 6a00af8e10..7e50ef16c7 100644 --- a/packages/lib/services/session.ts +++ b/packages/lib/services/session.ts @@ -10,6 +10,10 @@ import { revalidateTag, unstable_cache } from "next/cache"; import { cache } from "react"; import { validateInputs } from "../utils/validate"; +const halfHourInSeconds = 60 * 30; + +const getSessionCacheKey = (sessionId: string): string[] => [sessionId]; + const select = { id: true, createdAt: true, @@ -45,10 +49,10 @@ export const getSessionCached = (sessionId: string) => async () => { return await getSession(sessionId); }, - [sessionId], + getSessionCacheKey(sessionId), { - tags: [sessionId], - revalidate: 30 * 60, // 30 minutes + tags: getSessionCacheKey(sessionId), + revalidate: halfHourInSeconds, // 30 minutes } )(); diff --git a/turbo.json b/turbo.json index 503697abfd..7f602d71cb 100644 --- a/turbo.json +++ b/turbo.json @@ -28,6 +28,7 @@ "outputs": ["dist/**", ".next/**"], "env": [ "CRON_SECRET", + "DEBUG", "PRISMA_GENERATE_DATAPROXY", "GITHUB_ID", "GITHUB_SECRET",