From ce47ae4e5ef7d2b2c48aa41596026be34bd0b914 Mon Sep 17 00:00:00 2001 From: pandeymangg Date: Sat, 16 Sep 2023 17:18:36 +0530 Subject: [PATCH 1/2] fix: caching in actions endpoint --- 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 | 25 +++++++- 5 files changed, 142 insertions(+), 41 deletions(-) diff --git a/apps/web/app/api/v1/js/actions/route.ts b/apps/web/app/api/v1/js/actions/route.ts index a8d5a0af615..883f95260c0 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 ec9aa79bb98..46d63e3edf7 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.NODE_ENV !== "production" && { + log: ["query", "info"], + }), }).$extends(withAccelerate()); }; diff --git a/packages/lib/services/actionClass.ts b/packages/lib/services/actionClass.ts index 6565c948daf..dfc14ea8f36 100644 --- a/packages/lib/services/actionClass.ts +++ b/packages/lib/services/actionClass.ts @@ -7,7 +7,13 @@ import { validateInputs } from "../utils/validate"; import { ZId } from "@formbricks/types/v1/environment"; import { cache } from "react"; import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors"; -import { revalidateTag } from "next/cache"; +import { revalidateTag, unstable_cache } from "next/cache"; + +const getActionClassCacheKey = (name: string, environmentId: string): string[] => [ + `actionClass-${name}-env-${environmentId}`, +]; + +const halfHourInSeconds = 60 * 30; const select = { id: true, @@ -122,3 +128,23 @@ export const updateActionClass = async ( throw new DatabaseError(`Database error when updating an action for environment ${environmentId}`); } }; + +export const getActionClassCached = async (name: string, environmentId: string) => { + const cachedActionClassGetter = unstable_cache( + async () => { + return await prisma.eventClass.findFirst({ + where: { + name, + environmentId, + }, + }); + }, + getActionClassCacheKey(name, environmentId), + { + tags: getActionClassCacheKey(name, environmentId), + revalidate: halfHourInSeconds, + } + ); + + return await cachedActionClassGetter(); +}; diff --git a/packages/lib/services/actions.ts b/packages/lib/services/actions.ts index c254e27cdee..ca5ccfeb7ae 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 { 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(`actionClass-${name}-env-${environmentId}`); +}; diff --git a/packages/lib/services/session.ts b/packages/lib/services/session.ts index 8b1dff515f3..df864907f54 100644 --- a/packages/lib/services/session.ts +++ b/packages/lib/services/session.ts @@ -8,7 +8,11 @@ import { Prisma } from "@prisma/client"; import { cache } from "react"; import { validateInputs } from "../utils/validate"; import { ZId } from "@formbricks/types/v1/environment"; -import { revalidateTag } from "next/cache"; +import { revalidateTag, unstable_cache } from "next/cache"; + +const halfHourInSeconds = 60 * 30; + +const getSessionCacheKey = (sessionId: string): string[] => [sessionId]; const select = { id: true, @@ -149,3 +153,22 @@ export const extendSession = async (sessionId: string): Promise => { throw error; } }; + +export const getSessionCached = async (sessionId: string) => { + const cachedSessionGetter = unstable_cache( + async () => { + return await prisma.session.findFirst({ + where: { + id: sessionId, + }, + }); + }, + getSessionCacheKey(sessionId), + { + tags: getSessionCacheKey(sessionId), + revalidate: halfHourInSeconds, + } + ); + + return await cachedSessionGetter(); +}; From bebbd07e76765f71c7341390a73ba9a73dd2d36d Mon Sep 17 00:00:00 2001 From: Matthias Nannt Date: Sat, 16 Sep 2023 22:34:42 +0900 Subject: [PATCH 2/2] refactor --- packages/database/src/client.ts | 2 +- packages/lib/services/actionClass.ts | 20 ++++++++++---------- packages/lib/services/actions.ts | 4 ++-- turbo.json | 1 + 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/database/src/client.ts b/packages/database/src/client.ts index 46d63e3edf7..d77b9714509 100644 --- a/packages/database/src/client.ts +++ b/packages/database/src/client.ts @@ -4,7 +4,7 @@ import { withAccelerate } from "@prisma/extension-accelerate"; const prismaClientSingleton = () => { return new PrismaClient({ datasources: { db: { url: process.env.DATABASE_URL } }, - ...(process.env.NODE_ENV !== "production" && { + ...(process.env.DEBUG === "1" && { log: ["query", "info"], }), }).$extends(withAccelerate()); diff --git a/packages/lib/services/actionClass.ts b/packages/lib/services/actionClass.ts index a2a716a6d02..d3381fcc46c 100644 --- a/packages/lib/services/actionClass.ts +++ b/packages/lib/services/actionClass.ts @@ -9,12 +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[] => [ - `actionClass-${name}-env-${environmentId}`, + getActionClassCacheTag(name, environmentId), ]; -const halfHourInSeconds = 60 * 30; - const getActionClassesCacheTag = (environmentId: string): string => `env-${environmentId}-actionClasses`; const getActionClassesCacheKey = (environmentId: string): string[] => [ getActionClassesCacheTag(environmentId), @@ -58,7 +60,7 @@ export const getActionClassesCached = (environmentId: string) => getActionClassesCacheKey(environmentId), { tags: getActionClassesCacheKey(environmentId), - revalidate: 30 * 60, // 30 minutes + revalidate: halfHourInSeconds, } )(); @@ -138,6 +140,7 @@ export const updateActionClass = async ( }); // revalidate cache + revalidateTag(getActionClassCacheTag(result.name, environmentId)); revalidateTag(getActionClassesCacheTag(environmentId)); return result; @@ -146,8 +149,8 @@ export const updateActionClass = async ( } }; -export const getActionClassCached = async (name: string, environmentId: string) => { - const cachedActionClassGetter = unstable_cache( +export const getActionClassCached = async (name: string, environmentId: string) => + unstable_cache( async () => { return await prisma.eventClass.findFirst({ where: { @@ -161,7 +164,4 @@ export const getActionClassCached = async (name: string, environmentId: string) tags: getActionClassCacheKey(name, environmentId), revalidate: halfHourInSeconds, } - ); - - return await cachedActionClassGetter(); -}; + )(); diff --git a/packages/lib/services/actions.ts b/packages/lib/services/actions.ts index ca5ccfeb7ae..b4c5ce28c83 100644 --- a/packages/lib/services/actions.ts +++ b/packages/lib/services/actions.ts @@ -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 { getActionClassCached } from "../services/actionClass"; +import { getActionClassCacheTag, getActionClassCached } from "../services/actionClass"; import { getSessionCached } from "../services/session"; export const getActionsByEnvironmentId = cache( @@ -128,5 +128,5 @@ export const createAction = async (data: TJsActionInput) => { // revalidate cache revalidateTag(sessionId); - revalidateTag(`actionClass-${name}-env-${environmentId}`); + revalidateTag(getActionClassCacheTag(name, environmentId)); }; diff --git a/turbo.json b/turbo.json index 503697abfd9..7f602d71cbb 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",