mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-22 19:39:01 -05:00
0225362a92
* feat: caching in surveys * fix: fixes unstable_cache date parsing error * fix: fixes survey revalidation in displays and responses * fix: fixes survey cache * fix: adds comments * fix: response cache tag * fix cache validation and tag naming * move TSurveysWithAnalytics to TSurveys * add caching to more services --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
351 lines
8.0 KiB
TypeScript
351 lines
8.0 KiB
TypeScript
import "server-only";
|
|
|
|
import { prisma } from "@formbricks/database";
|
|
import { ZId } from "@formbricks/types/v1/environment";
|
|
import { DatabaseError } from "@formbricks/types/v1/errors";
|
|
import { TPerson, TPersonUpdateInput } from "@formbricks/types/v1/people";
|
|
import { Prisma } from "@prisma/client";
|
|
import { revalidateTag, unstable_cache } from "next/cache";
|
|
import { cache } from "react";
|
|
import { PEOPLE_PER_PAGE } from "../constants";
|
|
import { validateInputs } from "../utils/validate";
|
|
import { getAttributeClassByName } from "./attributeClass";
|
|
|
|
export const selectPerson = {
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
environmentId: true,
|
|
attributes: {
|
|
where: {
|
|
attributeClass: {
|
|
archived: false,
|
|
},
|
|
},
|
|
select: {
|
|
value: true,
|
|
attributeClass: {
|
|
select: {
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
type TransformPersonInput = {
|
|
id: string;
|
|
environmentId: string;
|
|
attributes: {
|
|
value: string;
|
|
attributeClass: {
|
|
name: string;
|
|
};
|
|
}[];
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
export const transformPrismaPerson = (person: TransformPersonInput): TPerson => {
|
|
const attributes = person.attributes.reduce(
|
|
(acc, attr) => {
|
|
acc[attr.attributeClass.name] = attr.value;
|
|
return acc;
|
|
},
|
|
{} as Record<string, string | number>
|
|
);
|
|
|
|
return {
|
|
id: person.id,
|
|
attributes: attributes,
|
|
environmentId: person.environmentId,
|
|
createdAt: person.createdAt,
|
|
updatedAt: person.updatedAt,
|
|
};
|
|
};
|
|
|
|
export const getPerson = cache(async (personId: string): Promise<TPerson | null> => {
|
|
validateInputs([personId, ZId]);
|
|
try {
|
|
const personPrisma = await prisma.person.findUnique({
|
|
where: {
|
|
id: personId,
|
|
},
|
|
select: selectPerson,
|
|
});
|
|
|
|
if (!personPrisma) {
|
|
return null;
|
|
}
|
|
|
|
const person = transformPrismaPerson(personPrisma);
|
|
|
|
return person;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError("Database operation failed");
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
const getPersonCacheKey = (personId: string): string[] => [personId];
|
|
|
|
export const getPersonCached = async (personId: string) =>
|
|
await unstable_cache(
|
|
async () => {
|
|
return await getPerson(personId);
|
|
},
|
|
getPersonCacheKey(personId),
|
|
{
|
|
tags: getPersonCacheKey(personId),
|
|
revalidate: 30 * 60, // 30 minutes
|
|
}
|
|
)();
|
|
|
|
export const getPeople = cache(async (environmentId: string, page: number = 1): Promise<TPerson[]> => {
|
|
validateInputs([environmentId, ZId]);
|
|
try {
|
|
const itemsPerPage = PEOPLE_PER_PAGE;
|
|
const people = await prisma.person.findMany({
|
|
where: {
|
|
environmentId: environmentId,
|
|
},
|
|
select: selectPerson,
|
|
take: itemsPerPage,
|
|
skip: itemsPerPage * (page - 1),
|
|
});
|
|
|
|
if (!people || people.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const transformedPeople: TPerson[] = people
|
|
.map(transformPrismaPerson)
|
|
.filter((person: TPerson | null): person is TPerson => person !== null);
|
|
|
|
return transformedPeople;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError("Database operation failed");
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
export const getPeopleCount = cache(async (environmentId: string): Promise<number> => {
|
|
validateInputs([environmentId, ZId]);
|
|
try {
|
|
const totalCount = await prisma.person.count({
|
|
where: {
|
|
environmentId: environmentId,
|
|
},
|
|
});
|
|
return totalCount;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
export const createPerson = async (environmentId: string): Promise<TPerson> => {
|
|
validateInputs([environmentId, ZId]);
|
|
try {
|
|
const personPrisma = await prisma.person.create({
|
|
data: {
|
|
environment: {
|
|
connect: {
|
|
id: environmentId,
|
|
},
|
|
},
|
|
},
|
|
select: selectPerson,
|
|
});
|
|
|
|
const person = transformPrismaPerson(personPrisma);
|
|
|
|
if (person) {
|
|
// revalidate person
|
|
revalidateTag(person.id);
|
|
}
|
|
|
|
return person;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const deletePerson = async (personId: string): Promise<void> => {
|
|
validateInputs([personId, ZId]);
|
|
try {
|
|
await prisma.person.delete({
|
|
where: {
|
|
id: personId,
|
|
},
|
|
});
|
|
|
|
// revalidate person
|
|
revalidateTag(personId);
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError("Database operation failed");
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const updatePerson = async (personId: string, personInput: TPersonUpdateInput): Promise<TPerson> => {
|
|
try {
|
|
const personPrisma = await prisma.person.update({
|
|
where: {
|
|
id: personId,
|
|
},
|
|
data: personInput,
|
|
select: selectPerson,
|
|
});
|
|
|
|
const person = transformPrismaPerson(personPrisma);
|
|
return person;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError("Database operation failed");
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
export const getOrCreatePersonByUserId = async (userId: string, environmentId: string): Promise<TPerson> => {
|
|
// Check if a person with the userId attribute exists
|
|
const personPrisma = await prisma.person.findFirst({
|
|
where: {
|
|
environmentId,
|
|
attributes: {
|
|
some: {
|
|
attributeClass: {
|
|
name: "userId",
|
|
},
|
|
value: userId,
|
|
},
|
|
},
|
|
},
|
|
select: selectPerson,
|
|
});
|
|
|
|
if (personPrisma) {
|
|
const person = transformPrismaPerson(personPrisma);
|
|
return person;
|
|
} else {
|
|
// Create a new person with the userId attribute
|
|
const userIdAttributeClass = await getAttributeClassByName(environmentId, "userId");
|
|
|
|
if (!userIdAttributeClass) {
|
|
throw new Error("Attribute class not found for the given environmentId");
|
|
}
|
|
|
|
const personPrisma = await prisma.person.create({
|
|
data: {
|
|
environment: {
|
|
connect: {
|
|
id: environmentId,
|
|
},
|
|
},
|
|
attributes: {
|
|
create: [
|
|
{
|
|
attributeClass: {
|
|
connect: {
|
|
id: userIdAttributeClass.id,
|
|
},
|
|
},
|
|
value: userId,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
select: selectPerson,
|
|
});
|
|
|
|
if (personPrisma) {
|
|
// revalidate person
|
|
revalidateTag(personPrisma.id);
|
|
}
|
|
|
|
return transformPrismaPerson(personPrisma);
|
|
}
|
|
};
|
|
|
|
export const getMonthlyActivePeopleCount = async (environmentId: string): Promise<number> =>
|
|
await unstable_cache(
|
|
async () => {
|
|
const now = new Date();
|
|
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
|
|
const aggregations = await prisma.person.aggregate({
|
|
_count: {
|
|
id: true,
|
|
},
|
|
where: {
|
|
environmentId,
|
|
sessions: {
|
|
some: {
|
|
createdAt: {
|
|
gte: firstDayOfMonth,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return aggregations._count.id;
|
|
},
|
|
[`environments-${environmentId}-mau`],
|
|
{
|
|
tags: [`environments-${environmentId}-mau`],
|
|
revalidate: 60 * 60 * 6, // 6 hours
|
|
}
|
|
)();
|
|
|
|
export const updatePersonAttribute = async (
|
|
personId: string,
|
|
attributeClassId: string,
|
|
value: string
|
|
): Promise<void> => {
|
|
await prisma.attribute.upsert({
|
|
where: {
|
|
attributeClassId_personId: {
|
|
attributeClassId,
|
|
personId,
|
|
},
|
|
},
|
|
update: {
|
|
value,
|
|
},
|
|
create: {
|
|
attributeClass: {
|
|
connect: {
|
|
id: attributeClassId,
|
|
},
|
|
},
|
|
person: {
|
|
connect: {
|
|
id: personId,
|
|
},
|
|
},
|
|
value,
|
|
},
|
|
});
|
|
|
|
// revalidate person
|
|
revalidateTag(personId);
|
|
};
|