mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 02:10:12 -06:00
chore: refactor sync endpoint to use existing services (#1417)
This commit is contained in:
@@ -2,6 +2,7 @@ import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { createAttributeClass, getAttributeClassByNameCached } from "@formbricks/lib/attributeClass/service";
|
||||
import { personCache } from "@formbricks/lib/person/cache";
|
||||
import { getPerson, updatePersonAttribute } from "@formbricks/lib/person/service";
|
||||
import { ZJsPeopleAttributeInput } from "@formbricks/types/js";
|
||||
import { NextResponse } from "next/server";
|
||||
@@ -50,6 +51,11 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {
|
||||
|
||||
const state = await getUpdatedState(environmentId, personId, sessionId);
|
||||
|
||||
personCache.revalidate({
|
||||
id: state.person.id,
|
||||
environmentId,
|
||||
});
|
||||
|
||||
return responses.successResponse({ ...state }, true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { personCache } from "@formbricks/lib/person/cache";
|
||||
import { deletePerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/person/service";
|
||||
import { ZJsPeopleUserIdInput } from "@formbricks/types/js";
|
||||
import { revalidateTag } from "next/cache";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
@@ -92,13 +92,13 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {
|
||||
|
||||
const transformedPerson = transformPrismaPerson(returnedPerson);
|
||||
|
||||
if (transformedPerson) {
|
||||
// revalidate person
|
||||
revalidateTag(transformedPerson.id);
|
||||
}
|
||||
|
||||
const state = await getUpdatedState(environmentId, transformedPerson.id, sessionId);
|
||||
|
||||
personCache.revalidate({
|
||||
id: transformedPerson.id,
|
||||
environmentId: environmentId,
|
||||
});
|
||||
|
||||
return responses.successResponse({ ...state }, true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -1,127 +1,86 @@
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { selectSurvey } from "@formbricks/lib/survey/service";
|
||||
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import { displayCache } from "@formbricks/lib/display/cache";
|
||||
import { getDisplaysByPersonId } from "@formbricks/lib/display/service";
|
||||
import { getProductByEnvironmentIdCached, getProductCacheTag } from "@formbricks/lib/product/service";
|
||||
import { getSurveyCacheTag, getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
import { TPerson } from "@formbricks/types/people";
|
||||
import { unstable_cache } from "next/cache";
|
||||
|
||||
const getSurveysCacheTags = (environmentId: string, personId: string): string[] => [
|
||||
`environments-${environmentId}-surveys`,
|
||||
`environments-${environmentId}-product`,
|
||||
personId,
|
||||
];
|
||||
// Helper function to calculate difference in days between two dates
|
||||
const diffInDays = (date1: Date, date2: Date) => {
|
||||
const diffTime = Math.abs(date2.getTime() - date1.getTime());
|
||||
return Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
};
|
||||
|
||||
const getSurveysCacheKey = (environmentId: string, personId: string): string[] => [
|
||||
`environments-${environmentId}-person-${personId}-syncSurveys`,
|
||||
];
|
||||
|
||||
export const getSurveysCached = (environmentId: string, person: TPerson) =>
|
||||
export const getSyncSurveysCached = (environmentId: string, person: TPerson) =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
return await getSurveys(environmentId, person);
|
||||
return await getSyncSurveys(environmentId, person);
|
||||
},
|
||||
getSurveysCacheKey(environmentId, person.id),
|
||||
[`getSyncSurveysCached-${environmentId}-${person.id}`],
|
||||
{
|
||||
tags: getSurveysCacheTags(environmentId, person.id),
|
||||
revalidate: 30 * 60,
|
||||
tags: [
|
||||
displayCache.tag.byPersonId(person.id),
|
||||
getSurveyCacheTag(environmentId),
|
||||
getProductCacheTag(environmentId),
|
||||
],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
export const getSurveys = async (environmentId: string, person: TPerson): Promise<TSurveyWithTriggers[]> => {
|
||||
export const getSyncSurveys = async (
|
||||
environmentId: string,
|
||||
person: TPerson
|
||||
): Promise<TSurveyWithTriggers[]> => {
|
||||
// get recontactDays from product
|
||||
const product = await prisma.product.findFirst({
|
||||
where: {
|
||||
environments: {
|
||||
some: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
recontactDays: true,
|
||||
},
|
||||
});
|
||||
const product = await getProductByEnvironmentIdCached(environmentId);
|
||||
|
||||
if (!product) {
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
// get all surveys that meet the displayOption criteria
|
||||
const potentialSurveys = await prisma.survey.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
environmentId,
|
||||
type: "web",
|
||||
status: "inProgress",
|
||||
displayOption: "respondMultiple",
|
||||
},
|
||||
{
|
||||
environmentId,
|
||||
type: "web",
|
||||
status: "inProgress",
|
||||
displayOption: "displayOnce",
|
||||
displays: { none: { personId: person.id } },
|
||||
},
|
||||
{
|
||||
environmentId,
|
||||
type: "web",
|
||||
status: "inProgress",
|
||||
displayOption: "displayMultiple",
|
||||
displays: { none: { personId: person.id, status: "responded" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
...selectSurvey,
|
||||
attributeFilters: {
|
||||
select: {
|
||||
id: true,
|
||||
condition: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
displays: {
|
||||
where: {
|
||||
personId: person.id,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 1,
|
||||
select: {
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
let surveys = await getSurveys(environmentId);
|
||||
|
||||
// filtered surveys for running and web
|
||||
surveys = surveys.filter((survey) => survey.status === "inProgress" && survey.type === "web");
|
||||
|
||||
const displays = await getDisplaysByPersonId(person.id);
|
||||
|
||||
// filter surveys that meet the displayOption criteria
|
||||
surveys = surveys.filter((survey) => {
|
||||
if (survey.displayOption === "respondMultiple") {
|
||||
return true;
|
||||
} else if (survey.displayOption === "displayOnce") {
|
||||
return displays.filter((display) => display.surveyId === survey.id).length === 0;
|
||||
} else if (survey.displayOption === "displayMultiple") {
|
||||
return (
|
||||
displays.filter((display) => display.surveyId === survey.id && display.responseId !== null).length ===
|
||||
0
|
||||
);
|
||||
} else {
|
||||
throw Error("Invalid displayOption");
|
||||
}
|
||||
});
|
||||
|
||||
// get last display for this person
|
||||
const lastDisplayPerson = await prisma.display.findFirst({
|
||||
where: {
|
||||
personId: person.id,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
select: {
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
const attributeClasses = await getAttributeClasses(environmentId);
|
||||
|
||||
// filter surveys that meet the attributeFilters criteria
|
||||
const potentialSurveysWithAttributes = potentialSurveys.filter((survey) => {
|
||||
const potentialSurveysWithAttributes = surveys.filter((survey) => {
|
||||
const attributeFilters = survey.attributeFilters;
|
||||
if (attributeFilters.length === 0) {
|
||||
return true;
|
||||
}
|
||||
// check if meets all attribute filters criterias
|
||||
return attributeFilters.every((attributeFilter) => {
|
||||
const personAttributeValue = person.attributes[attributeFilter.attributeClass.name];
|
||||
const attributeClassName = attributeClasses.find(
|
||||
(attributeClass) => attributeClass.id === attributeFilter.attributeClassId
|
||||
)?.name;
|
||||
if (!attributeClassName) {
|
||||
throw Error("Invalid attribute filter class");
|
||||
}
|
||||
const personAttributeValue = person.attributes[attributeClassName];
|
||||
if (attributeFilter.condition === "equals") {
|
||||
return personAttributeValue === attributeFilter.value;
|
||||
} else if (attributeFilter.condition === "notEquals") {
|
||||
@@ -132,46 +91,24 @@ export const getSurveys = async (environmentId: string, person: TPerson): Promis
|
||||
});
|
||||
});
|
||||
|
||||
const latestDisplay = displays[0];
|
||||
|
||||
// filter surveys that meet the recontactDays criteria
|
||||
const surveys: TSurveyWithTriggers[] = potentialSurveysWithAttributes
|
||||
.filter((survey) => {
|
||||
if (!lastDisplayPerson) {
|
||||
// no display yet - always display
|
||||
return true;
|
||||
} else if (survey.recontactDays !== null) {
|
||||
// if recontactDays is set on survey, use that
|
||||
const lastDisplaySurvey = survey.displays[0];
|
||||
if (!lastDisplaySurvey) {
|
||||
// no display yet - always display
|
||||
return true;
|
||||
}
|
||||
const lastDisplayDate = new Date(lastDisplaySurvey.createdAt);
|
||||
const currentDate = new Date();
|
||||
const diffTime = Math.abs(currentDate.getTime() - lastDisplayDate.getTime());
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
return diffDays >= survey.recontactDays;
|
||||
} else if (product.recontactDays !== null) {
|
||||
// if recontactDays is not set in survey, use product recontactDays
|
||||
const lastDisplayDate = new Date(lastDisplayPerson.createdAt);
|
||||
const currentDate = new Date();
|
||||
const diffTime = Math.abs(currentDate.getTime() - lastDisplayDate.getTime());
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
return diffDays >= product.recontactDays;
|
||||
} else {
|
||||
// if recontactDays is not set in survey or product, always display
|
||||
surveys = potentialSurveysWithAttributes.filter((survey) => {
|
||||
if (!latestDisplay) {
|
||||
return true;
|
||||
} else if (survey.recontactDays !== null) {
|
||||
const lastDisplaySurvey = displays.filter((display) => display.surveyId === survey.id)[0];
|
||||
if (!lastDisplaySurvey) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((survey) => ({
|
||||
...survey,
|
||||
singleUse: survey.singleUse ? JSON.parse(JSON.stringify(survey.singleUse)) : null,
|
||||
triggers: survey.triggers.map((trigger) => trigger.eventClass),
|
||||
attributeFilters: survey.attributeFilters.map((af) => ({
|
||||
...af,
|
||||
attributeClassId: af.attributeClass.id,
|
||||
attributeClass: undefined,
|
||||
})),
|
||||
}));
|
||||
return diffInDays(new Date(), new Date(lastDisplaySurvey.createdAt)) >= survey.recontactDays;
|
||||
} else if (product.recontactDays !== null) {
|
||||
return diffInDays(new Date(), new Date(latestDisplay.createdAt)) >= product.recontactDays;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return surveys;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSurveysCached } from "@/app/api/v1/js/sync/lib/surveys";
|
||||
import { getSyncSurveysCached } from "@/app/api/v1/js/sync/lib/surveys";
|
||||
import { MAU_LIMIT } from "@formbricks/lib/constants";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
@@ -100,7 +100,7 @@ export const getUpdatedState = async (
|
||||
|
||||
// get/create rest of the state
|
||||
const [surveys, noCodeActionClasses, product] = await Promise.all([
|
||||
getSurveysCached(environmentId, person),
|
||||
getSyncSurveysCached(environmentId, person),
|
||||
getActionClasses(environmentId),
|
||||
getProductByEnvironmentIdCached(environmentId),
|
||||
]);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getSettings } from "@/app/lib/api/clientSettings";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { personCache } from "@formbricks/lib/person/cache";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -128,6 +129,11 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const person = attribute.person;
|
||||
|
||||
personCache.revalidate({
|
||||
id: person.id,
|
||||
environmentId: person.environmentId,
|
||||
});
|
||||
|
||||
const settings = await getSettings(environmentId, person.id);
|
||||
|
||||
// return updated person
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getSettings } from "@/app/lib/api/clientSettings";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { personCache } from "@formbricks/lib/person/cache";
|
||||
import { deletePerson } from "@formbricks/lib/person/service";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -76,11 +78,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
// delete old person
|
||||
await prisma.person.delete({
|
||||
where: {
|
||||
id: personId,
|
||||
},
|
||||
});
|
||||
await deletePerson(personId);
|
||||
person = existingPerson;
|
||||
} else {
|
||||
// update person
|
||||
@@ -122,6 +120,11 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
|
||||
personCache.revalidate({
|
||||
id: person.id,
|
||||
environmentId: person.environmentId,
|
||||
});
|
||||
|
||||
const settings = await getSettings(environmentId, person.id);
|
||||
|
||||
// return updated person and settings
|
||||
|
||||
@@ -159,10 +159,10 @@ export const markDisplayResponded = async (displayId: string): Promise<TDisplay>
|
||||
}
|
||||
};
|
||||
|
||||
export const getDisplaysOfPerson = async (
|
||||
export const getDisplaysByPersonId = async (
|
||||
personId: string,
|
||||
page?: number
|
||||
): Promise<TDisplaysWithSurveyName[] | null> => {
|
||||
): Promise<TDisplaysWithSurveyName[]> => {
|
||||
const displays = await unstable_cache(
|
||||
async () => {
|
||||
validateInputs([personId, ZId], [page, ZOptionalNumber]);
|
||||
@@ -187,6 +187,9 @@ export const getDisplaysOfPerson = async (
|
||||
},
|
||||
take: page ? ITEMS_PER_PAGE : undefined,
|
||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
if (!displaysPrisma) {
|
||||
@@ -217,7 +220,7 @@ export const getDisplaysOfPerson = async (
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getDisplaysOfPerson-${personId}-${page}`],
|
||||
[`getDisplaysByPersonId-${personId}-${page}`],
|
||||
{
|
||||
tags: [displayCache.tag.byPersonId(personId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
|
||||
Reference in New Issue
Block a user