Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: enable caching in all js endpoints to reduce database calls #821

Merged
merged 6 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 26 additions & 61 deletions apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { getSurveys } from "@/app/api/v1/js/surveys";
import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { prisma } from "@formbricks/database";
import { getActionClasses } from "@formbricks/lib/services/actionClass";
import { getPerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/services/person";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { extendSession } from "@formbricks/lib/services/session";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { createAttributeClass, getAttributeClassByNameCached } from "@formbricks/lib/services/attributeClass";
import { getPersonCached } from "@formbricks/lib/services/person";
import { TJsState, ZJsPeopleAttributeInput } from "@formbricks/types/v1/js";
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";
Expand All @@ -32,45 +30,25 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {

const { environmentId, sessionId, key, value } = inputValidation.data;

const existingPerson = await getPerson(personId);
const existingPerson = await getPersonCached(personId);

if (!existingPerson) {
return responses.notFoundResponse("Person", personId, true);
}

// find attribute class
let attributeClass = await prisma.attributeClass.findUnique({
where: {
name_environmentId: {
name: key,
environmentId,
},
},
select: {
id: true,
},
});
let attributeClass = await getAttributeClassByNameCached(environmentId, key);

// create new attribute class if not found
if (attributeClass === null) {
attributeClass = await prisma.attributeClass.create({
data: {
name: key,
type: "code",
environment: {
connect: {
id: environmentId,
},
},
},
select: {
id: true,
},
});
attributeClass = await createAttributeClass(environmentId, key, "code");
}

if (!attributeClass) {
return responses.internalServerErrorResponse("Unable to create attribute class", true);
}

// upsert attribute (update or create)
const attribute = await prisma.attribute.upsert({
await prisma.attribute.upsert({
where: {
attributeClassId_personId: {
attributeClassId: attributeClass.id,
Expand All @@ -93,40 +71,27 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {
},
value,
},
select: {
person: {
select: selectPerson,
},
},
});

const person = transformPrismaPerson(attribute.person);
// revalidate person
revalidateTag(personId);

if (person) {
// revalidate person
revalidateTag(person.id);
}

// get/create rest of the state
const [session, surveys, noCodeActionClasses, product] = await Promise.all([
extendSession(sessionId),
getSurveys(environmentId, person),
getActionClasses(environmentId),
getProductByEnvironmentId(environmentId),
]);
const syncRes = await fetch(`${WEBAPP_URL}/api/v1/js/sync`, {
method: "POST",
body: JSON.stringify({
environmentId,
personId,
sessionId,
}),
});

if (!product) {
return responses.notFoundResponse("ProductByEnvironmentId", environmentId, true);
if (!syncRes.ok) {
throw new Error("Unable to get latest state from sync");
}

// return state
const state: TJsState = {
person,
session,
surveys,
noCodeActionClasses: noCodeActionClasses.filter((actionClass) => actionClass.type === "noCode"),
product,
};
const syncJson = await syncRes.json();
const state: TJsState = syncJson.data;

return responses.successResponse({ ...state }, true);
} catch (error) {
console.error(error);
Expand Down
36 changes: 15 additions & 21 deletions apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { getSurveys } from "@/app/api/v1/js/surveys";
import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { prisma } from "@formbricks/database";
import { getActionClasses } from "@formbricks/lib/services/actionClass";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { deletePerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/services/person";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { extendSession } from "@formbricks/lib/services/session";
import { TJsState, ZJsPeopleUserIdInput } from "@formbricks/types/v1/js";
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";
Expand Down Expand Up @@ -66,6 +63,7 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {

// delete old person
await deletePerson(personId);

returnedPerson = existingPerson;
} else {
// update person with userId
Expand Down Expand Up @@ -99,26 +97,22 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {
revalidateTag(person.id);
}

// get/create rest of the state
const [session, surveys, noCodeActionClasses, product] = await Promise.all([
extendSession(sessionId),
getSurveys(environmentId, person),
getActionClasses(environmentId),
getProductByEnvironmentId(environmentId),
]);
const syncRes = await fetch(`${WEBAPP_URL}/api/v1/js/sync`, {
method: "POST",
body: JSON.stringify({
environmentId,
personId,
sessionId,
}),
});

if (!product) {
return responses.notFoundResponse("ProductByEnvironmentId", environmentId, true);
if (!syncRes.ok) {
throw new Error("Unable to get latest state from sync");
}

// return state
const state: TJsState = {
person,
session,
surveys,
noCodeActionClasses: noCodeActionClasses.filter((actionClass) => actionClass.type === "noCode"),
product,
};
const syncJson = await syncRes.json();
const state: TJsState = syncJson.data;

return responses.successResponse({ ...state }, true);
} catch (error) {
console.error(error);
Expand Down
23 changes: 23 additions & 0 deletions apps/web/app/api/v1/js/surveys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@ import { prisma } from "@formbricks/database";
import { selectSurvey } from "@formbricks/lib/services/survey";
import { TPerson } from "@formbricks/types/v1/people";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { unstable_cache } from "next/cache";

const getSurveysCacheTags = (environmentId: string, personId: string): string[] => [
`env-${environmentId}-surveys`,
`env-${environmentId}-product`,
personId,
];

const getSurveysCacheKey = (environmentId: string, personId: string): string[] => [
`env-${environmentId}-person-${personId}-syncSurveys`,
];

export const getSurveysCached = (environmentId: string, person: TPerson) =>
unstable_cache(
async () => {
return await getSurveys(environmentId, person);
},
getSurveysCacheKey(environmentId, person.id),
{
tags: getSurveysCacheTags(environmentId, person.id),
revalidate: 30 * 60,
}
)();

export const getSurveys = async (environmentId: string, person: TPerson): Promise<TSurvey[]> => {
// get recontactDays from product
Expand Down
Loading
Loading