mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 05:40:02 -06:00
231 lines
6.2 KiB
TypeScript
231 lines
6.2 KiB
TypeScript
import "server-only";
|
|
|
|
import { Prisma } from "@prisma/client";
|
|
import { unstable_cache } from "next/cache";
|
|
|
|
import { prisma } from "@formbricks/database";
|
|
import { TAttributes, ZAttributes } from "@formbricks/types/attributes";
|
|
import { ZString } from "@formbricks/types/common";
|
|
import { ZId } from "@formbricks/types/environment";
|
|
import { DatabaseError } from "@formbricks/types/errors";
|
|
|
|
import { attributeCache } from "../attribute/cache";
|
|
import { attributeClassCache } from "../attributeClass/cache";
|
|
import { getAttributeClassByName, getAttributeClasses } from "../attributeClass/service";
|
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
|
import { getPerson, getPersonByUserId } from "../person/service";
|
|
import { validateInputs } from "../utils/validate";
|
|
|
|
export const selectAttribute: Prisma.AttributeSelect = {
|
|
value: true,
|
|
attributeClass: {
|
|
select: {
|
|
name: true,
|
|
id: true,
|
|
},
|
|
},
|
|
};
|
|
|
|
// convert prisma attributes to a key-value object
|
|
const convertPrismaAttributes = (prismaAttributes: any): TAttributes => {
|
|
return prismaAttributes.reduce(
|
|
(acc, attr) => {
|
|
acc[attr.attributeClass.name] = attr.value;
|
|
return acc;
|
|
},
|
|
{} as Record<string, string | number>
|
|
);
|
|
};
|
|
|
|
export const getAttributes = async (personId: string): Promise<TAttributes> => {
|
|
return await unstable_cache(
|
|
async () => {
|
|
validateInputs([personId, ZId]);
|
|
|
|
try {
|
|
const prismaAttributes = await prisma.attribute.findMany({
|
|
where: {
|
|
personId,
|
|
},
|
|
select: selectAttribute,
|
|
});
|
|
|
|
return convertPrismaAttributes(prismaAttributes);
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
},
|
|
[`getAttributes-${personId}`],
|
|
{
|
|
tags: [attributeCache.tag.byPersonId(personId)],
|
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
|
}
|
|
)();
|
|
};
|
|
|
|
export const getAttributesByUserId = async (environmentId: string, userId: string): Promise<TAttributes> => {
|
|
return await unstable_cache(
|
|
async () => {
|
|
validateInputs([environmentId, ZId], [userId, ZString]);
|
|
|
|
const person = await getPersonByUserId(environmentId, userId);
|
|
|
|
if (!person) {
|
|
throw new Error("Person not found");
|
|
}
|
|
|
|
try {
|
|
const prismaAttributes = await prisma.attribute.findMany({
|
|
where: {
|
|
personId: person.id,
|
|
},
|
|
select: selectAttribute,
|
|
});
|
|
|
|
return convertPrismaAttributes(prismaAttributes);
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
},
|
|
[`getAttributesByUserId-${environmentId}-${userId}`],
|
|
{
|
|
tags: [attributeCache.tag.byEnvironmentIdAndUserId(environmentId, userId)],
|
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
|
}
|
|
)();
|
|
};
|
|
|
|
export const getAttribute = async (name: string, personId: string): Promise<string | undefined> => {
|
|
return await unstable_cache(
|
|
async () => {
|
|
validateInputs([name, ZString], [personId, ZId]);
|
|
|
|
const person = await getPerson(personId);
|
|
|
|
if (!person) {
|
|
throw new Error("Person not found");
|
|
}
|
|
|
|
const attributeClass = await getAttributeClassByName(person?.environmentId, name);
|
|
|
|
if (!attributeClass) {
|
|
return undefined;
|
|
}
|
|
|
|
try {
|
|
const prismaAttributes = await prisma.attribute.findFirst({
|
|
where: {
|
|
attributeClassId: attributeClass.id,
|
|
personId,
|
|
},
|
|
select: { value: true },
|
|
});
|
|
|
|
return prismaAttributes?.value;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
},
|
|
[`getAttribute-${name}-${personId}`],
|
|
{
|
|
tags: [attributeCache.tag.byNameAndPersonId(name, personId)],
|
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
|
}
|
|
)();
|
|
};
|
|
|
|
export const updateAttributes = async (personId: string, attributes: TAttributes): Promise<boolean> => {
|
|
validateInputs([personId, ZId], [attributes, ZAttributes]);
|
|
|
|
const person = await getPerson(personId);
|
|
|
|
if (!person) {
|
|
throw new Error("Person not found");
|
|
}
|
|
|
|
const environmentId = person.environmentId;
|
|
const userId = person.userId;
|
|
|
|
const attributeClasses = await getAttributeClasses(environmentId);
|
|
|
|
const attributeClassMap = new Map(attributeClasses.map((ac) => [ac.name, ac.id]));
|
|
const upsertOperations: Promise<any>[] = [];
|
|
|
|
for (const [name, value] of Object.entries(attributes)) {
|
|
const attributeClassId = attributeClassMap.get(name);
|
|
|
|
if (attributeClassId) {
|
|
// Class exists, perform an upsert operation
|
|
upsertOperations.push(
|
|
prisma.attribute
|
|
.upsert({
|
|
select: {
|
|
id: true,
|
|
},
|
|
where: {
|
|
personId_attributeClassId: {
|
|
personId,
|
|
attributeClassId,
|
|
},
|
|
},
|
|
update: {
|
|
value,
|
|
},
|
|
create: {
|
|
personId,
|
|
attributeClassId,
|
|
value,
|
|
},
|
|
})
|
|
.then(() => {
|
|
attributeCache.revalidate({ environmentId, personId, userId, name });
|
|
})
|
|
);
|
|
} else {
|
|
// Class does not exist, create new class and attribute
|
|
upsertOperations.push(
|
|
prisma.attributeClass
|
|
.create({
|
|
select: { id: true },
|
|
data: {
|
|
name,
|
|
type: "code",
|
|
environment: {
|
|
connect: {
|
|
id: environmentId,
|
|
},
|
|
},
|
|
attributes: {
|
|
create: {
|
|
personId,
|
|
value,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
.then(({ id }) => {
|
|
attributeClassCache.revalidate({ id, environmentId, name });
|
|
attributeCache.revalidate({ environmentId, personId, userId, name });
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
// Execute all upsert operations concurrently
|
|
await Promise.all(upsertOperations);
|
|
|
|
return true;
|
|
};
|