fix: cached service in attributes endpoint (#4728)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Anshuman Pandey
2025-02-28 22:30:08 +05:30
committed by GitHub
parent fccf0f1e39
commit ffa534d5eb
9 changed files with 137 additions and 84 deletions
@@ -1,7 +1,8 @@
import { getContactAttributes } from "@/modules/ee/contacts/lib/contact-attributes";
import { getContact } from "@/modules/ee/contacts/lib/contacts";
import { getTranslate } from "@/tolgee/server";
import { getResponsesByContactId } from "@formbricks/lib/response/service";
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
import { getContact, getContactAttributes } from "../../lib/contacts";
export const AttributesSection = async ({ contactId }: { contactId: string }) => {
const t = await getTranslate();
@@ -1,7 +1,8 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { AttributesSection } from "@/modules/ee/contacts/[contactId]/components/attributes-section";
import { DeleteContactButton } from "@/modules/ee/contacts/[contactId]/components/delete-contact-button";
import { getContact, getContactAttributes } from "@/modules/ee/contacts/lib/contacts";
import { getContactAttributes } from "@/modules/ee/contacts/lib/contact-attributes";
import { getContact } from "@/modules/ee/contacts/lib/contacts";
import { getContactIdentifier } from "@/modules/ee/contacts/lib/utils";
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
+3 -19
View File
@@ -1,11 +1,12 @@
import { contactAttributeCache } from "@/lib/cache/contact-attribute";
import { contactAttributeKeyCache } from "@/lib/cache/contact-attribute-key";
import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contact-attribute-keys";
import { hasEmailAttribute } from "@/modules/ee/contacts/lib/contact-attributes";
import { prisma } from "@formbricks/database";
import { MAX_ATTRIBUTE_CLASSES_PER_ENVIRONMENT } from "@formbricks/lib/constants";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId, ZString } from "@formbricks/types/common";
import { TContactAttributes, ZContactAttributes } from "@formbricks/types/contact-attribute";
import { getContactAttributeKeys } from "./contacts";
export const updateAttributes = async (
contactId: string,
@@ -24,24 +25,7 @@ export const updateAttributes = async (
const [contactAttributeKeys, existingEmailAttribute] = await Promise.all([
getContactAttributeKeys(environmentId),
contactAttributesParam.email
? prisma.contactAttribute.findFirst({
where: {
AND: [
{
attributeKey: {
key: "email",
},
value: contactAttributesParam.email,
},
{
NOT: {
contactId,
},
},
],
},
select: { id: true },
})
? hasEmailAttribute(contactAttributesParam.email, environmentId, contactId)
: Promise.resolve(null),
]);
@@ -0,0 +1,20 @@
import { contactAttributeKeyCache } from "@/lib/cache/contact-attribute-key";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { cache } from "@formbricks/lib/cache";
import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key";
export const getContactAttributeKeys = reactCache(
(environmentId: string): Promise<TContactAttributeKey[]> =>
cache(
async () => {
return await prisma.contactAttributeKey.findMany({
where: { environmentId },
});
},
[`getContactAttributeKeys-${environmentId}`],
{
tags: [contactAttributeKeyCache.tag.byEnvironmentId(environmentId)],
}
)()
);
@@ -0,0 +1,92 @@
import { contactAttributeCache } from "@/lib/cache/contact-attribute";
import { contactAttributeKeyCache } from "@/lib/cache/contact-attribute-key";
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { cache } from "@formbricks/lib/cache";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId } from "@formbricks/types/common";
import { TContactAttributes } from "@formbricks/types/contact-attribute";
import { DatabaseError } from "@formbricks/types/errors";
import { ZUserEmail } from "@formbricks/types/user";
const selectContactAttribute = {
value: true,
attributeKey: {
select: {
key: true,
name: true,
},
},
} satisfies Prisma.ContactAttributeSelect;
export const getContactAttributes = reactCache((contactId: string) =>
cache(
async () => {
validateInputs([contactId, ZId]);
try {
const prismaAttributes = await prisma.contactAttribute.findMany({
where: {
contactId,
},
select: selectContactAttribute,
});
return prismaAttributes.reduce((acc, attr) => {
acc[attr.attributeKey.key] = attr.value;
return acc;
}, {}) as TContactAttributes;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getContactAttributes-${contactId}`],
{
tags: [contactAttributeCache.tag.byContactId(contactId)],
}
)()
);
export const hasEmailAttribute = reactCache(
async (email: string, environmentId: string, contactId: string): Promise<boolean> =>
cache(
async () => {
validateInputs([email, ZUserEmail], [environmentId, ZId], [contactId, ZId]);
const contactAttribute = await prisma.contactAttribute.findFirst({
where: {
AND: [
{
attributeKey: {
key: "email",
environmentId,
},
value: email,
},
{
NOT: {
contactId,
},
},
],
},
select: { id: true },
});
return !!contactAttribute;
},
[`hasEmailAttribute-${email}-${environmentId}-${contactId}`],
{
tags: [
contactAttributeKeyCache.tag.byEnvironmentIdAndKey(environmentId, "email"),
contactAttributeCache.tag.byEnvironmentId(environmentId),
contactAttributeCache.tag.byContactId(contactId),
],
}
)()
);
+14 -60
View File
@@ -9,8 +9,6 @@ import { cache } from "@formbricks/lib/cache";
import { ITEMS_PER_PAGE } from "@formbricks/lib/constants";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId, ZOptionalNumber, ZOptionalString } from "@formbricks/types/common";
import { TContactAttributes } from "@formbricks/types/contact-attribute";
import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key";
import { DatabaseError, ValidationError } from "@formbricks/types/errors";
import {
TContact,
@@ -39,16 +37,6 @@ const selectContact = {
},
} satisfies Prisma.ContactSelect;
const selectContactAttribute = {
value: true,
attributeKey: {
select: {
key: true,
name: true,
},
},
} satisfies Prisma.ContactAttributeSelect;
const buildContactWhereClause = (environmentId: string, search?: string): Prisma.ContactWhereInput => {
const whereClause: Prisma.ContactWhereInput = { environmentId };
@@ -149,6 +137,7 @@ export const deleteContact = async (contactId: string): Promise<TContact | null>
});
const contactUserId = contact.attributes.find((attr) => attr.attributeKey.key === "userId")?.value;
const contactAttributes = contact.attributes;
contactCache.revalidate({
id: contact.id,
@@ -156,6 +145,19 @@ export const deleteContact = async (contactId: string): Promise<TContact | null>
userId: contactUserId,
});
for (const attr of contactAttributes) {
contactAttributeCache.revalidate({
contactId: contact.id,
key: attr.attributeKey.key,
environmentId: contact.environmentId,
});
contactAttributeKeyCache.revalidate({
environmentId: contact.environmentId,
key: attr.attributeKey.key,
});
}
return contact;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
@@ -166,54 +168,6 @@ export const deleteContact = async (contactId: string): Promise<TContact | null>
}
};
export const getContactAttributes = reactCache((contactId: string) =>
cache(
async () => {
validateInputs([contactId, ZId]);
try {
const prismaAttributes = await prisma.contactAttribute.findMany({
where: {
contactId,
},
select: selectContactAttribute,
});
// return convertPrismaContactAttributes(prismaAttributes);
return prismaAttributes.reduce((acc, attr) => {
acc[attr.attributeKey.key] = attr.value;
return acc;
}, {}) as TContactAttributes;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getContactAttributes-${contactId}`],
{
tags: [contactAttributeCache.tag.byContactId(contactId)],
}
)()
);
export const getContactAttributeKeys = reactCache(
(environmentId: string): Promise<TContactAttributeKey[]> =>
cache(
async () => {
return await prisma.contactAttributeKey.findMany({
where: { environmentId },
});
},
[`getContactAttributeKeys-${environmentId}`],
{
tags: [contactAttributeKeyCache.tag.byEnvironmentId(environmentId)],
}
)()
);
export const createContactsFromCSV = async (
csvData: Record<string, string>[],
environmentId: string,
+2 -1
View File
@@ -1,7 +1,8 @@
import { contactCache } from "@/lib/cache/contact";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { UploadContactsCSVButton } from "@/modules/ee/contacts/components/upload-contacts-button";
import { getContactAttributeKeys, getContacts } from "@/modules/ee/contacts/lib/contacts";
import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contact-attribute-keys";
import { getContacts } from "@/modules/ee/contacts/lib/contacts";
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ContactsSecondaryNavigation } from "@/modules/ee/contacts/components/contacts-secondary-navigation";
import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contacts";
import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contact-attribute-keys";
import { SegmentTable } from "@/modules/ee/contacts/segments/components/segment-table";
import { getSegments } from "@/modules/ee/contacts/segments/lib/segments";
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contacts";
import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contact-attribute-keys";
import { getSegments } from "@/modules/ee/contacts/segments/lib/segments";
import { getIsContactsEnabled, getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils";
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";