add export contact book option (#318)

This commit is contained in:
KM Koushik
2025-12-14 10:08:54 +11:00
committed by GitHub
parent 461cd949e5
commit 1e79f13bd4
7 changed files with 303 additions and 60 deletions
+44 -1
View File
@@ -129,7 +129,7 @@ export const contactsRouter = createTRPCRouter({
subscribed: z.boolean().optional(),
}),
)
.max(10000),
.max(50000),
}),
)
.mutation(async ({ ctx: { contactBook, team }, input }) => {
@@ -161,4 +161,47 @@ export const contactsRouter = createTRPCRouter({
.mutation(async ({ input }) => {
return contactService.deleteContact(input.contactId);
}),
exportContacts: contactBookProcedure
.input(
z.object({
subscribed: z.boolean().optional(),
search: z.string().optional(),
}),
)
.query(async ({ ctx: { db }, input }) => {
const whereConditions: Prisma.ContactFindManyArgs["where"] = {
contactBookId: input.contactBookId,
...(input.subscribed !== undefined
? { subscribed: input.subscribed }
: {}),
...(input.search
? {
OR: [
{ email: { contains: input.search, mode: "insensitive" } },
{ firstName: { contains: input.search, mode: "insensitive" } },
{ lastName: { contains: input.search, mode: "insensitive" } },
],
}
: {}),
};
const contacts = await db.contact.findMany({
where: whereConditions,
select: {
email: true,
firstName: true,
lastName: true,
subscribed: true,
unsubscribeReason: true,
createdAt: true,
},
orderBy: {
createdAt: "desc",
},
take: 100000, // Limit to 100k contacts to prevent memory issues
});
return contacts;
}),
});
+29 -5
View File
@@ -11,8 +11,32 @@ export type ContactInput = {
export async function addOrUpdateContact(
contactBookId: string,
contact: ContactInput
contact: ContactInput,
) {
// Check if contact exists to handle subscribed logic
const existingContact = await db.contact.findUnique({
where: {
contactBookId_email: {
contactBookId,
email: contact.email,
},
},
select: {
subscribed: true,
},
});
// Determine subscribed value for update
// Only allow Yes→No transitions (allow unsubscribe, prevent re-subscribe)
let subscribedValue: boolean | undefined = contact.subscribed;
if (existingContact && contact.subscribed !== undefined) {
// Block No→Yes (prevent re-subscribe via CSV), allow all other transitions
if (!existingContact.subscribed && contact.subscribed) {
subscribedValue = undefined; // Block re-subscribe
}
// All other cases (Yes→No, Yes→Yes, No→No) are allowed naturally
}
const createdContact = await db.contact.upsert({
where: {
contactBookId_email: {
@@ -26,13 +50,13 @@ export async function addOrUpdateContact(
firstName: contact.firstName,
lastName: contact.lastName,
properties: contact.properties ?? {},
subscribed: contact.subscribed,
subscribed: contact.subscribed ?? true, // Default to subscribed for new contacts
},
update: {
firstName: contact.firstName,
lastName: contact.lastName,
properties: contact.properties ?? {},
subscribed: contact.subscribed,
...(subscribedValue !== undefined ? { subscribed: subscribedValue } : {}),
},
});
@@ -41,7 +65,7 @@ export async function addOrUpdateContact(
export async function updateContact(
contactId: string,
contact: Partial<ContactInput>
contact: Partial<ContactInput>,
) {
return db.contact.update({
where: {
@@ -62,7 +86,7 @@ export async function deleteContact(contactId: string) {
export async function bulkAddContacts(
contactBookId: string,
contacts: Array<ContactInput>,
teamId?: number
teamId?: number,
) {
await ContactQueueService.addBulkContactJobs(contactBookId, contacts, teamId);