feat: add contactBooks to sdk, add delete campaign public endpoint (#352)
* feat: add contactBooks to sdk, add delete campaign public endpoint * fix: pr review notes * refactor: pr feedback * feat: bulk delete/create contacts * refactor: rename a few methods for consistency * refactor: update openapi docs based on pr feedback * refactor: update open api docs, based on pr feedback * fix: delete campaign security issue * refactor: delete campaign requires team id (from context) * fix: enums
This commit is contained in:
@@ -171,8 +171,8 @@ export const campaignRouter = createTRPCRouter({
|
||||
return campaign;
|
||||
}),
|
||||
|
||||
deleteCampaign: campaignProcedure.mutation(async ({ input }) => {
|
||||
return await campaignService.deleteCampaign(input.campaignId);
|
||||
deleteCampaign: campaignProcedure.mutation(async ({ ctx: { team }, input }) => {
|
||||
return await campaignService.deleteCampaign(input.campaignId, team.id);
|
||||
}),
|
||||
|
||||
getCampaign: campaignProcedure.query(async ({ ctx: { db, team }, input }) => {
|
||||
|
||||
@@ -11,188 +11,201 @@ import * as contactService from "~/server/service/contact-service";
|
||||
import * as contactBookService from "~/server/service/contact-book-service";
|
||||
|
||||
export const contactsRouter = createTRPCRouter({
|
||||
getContactBooks: teamProcedure
|
||||
.input(z.object({ search: z.string().optional() }))
|
||||
.query(async ({ ctx: { team }, input }) => {
|
||||
return contactBookService.getContactBooks(team.id, input.search);
|
||||
}),
|
||||
getContactBooks: teamProcedure
|
||||
.input(z.object({ search: z.string().optional() }))
|
||||
.query(async ({ ctx: { team }, input }) => {
|
||||
return contactBookService.getContactBooks(team.id, input.search);
|
||||
}),
|
||||
|
||||
createContactBook: teamProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { team }, input }) => {
|
||||
const { name } = input;
|
||||
return contactBookService.createContactBook(team.id, name);
|
||||
}),
|
||||
createContactBook: teamProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { team }, input }) => {
|
||||
const { name } = input;
|
||||
return contactBookService.createContactBook(team.id, name);
|
||||
}),
|
||||
|
||||
getContactBookDetails: contactBookProcedure.query(
|
||||
async ({ ctx: { contactBook } }) => {
|
||||
const { totalContacts, unsubscribedContacts, campaigns } =
|
||||
await contactBookService.getContactBookDetails(contactBook.id);
|
||||
getContactBookDetails: contactBookProcedure.query(
|
||||
async ({ ctx: { contactBook } }) => {
|
||||
const { totalContacts, unsubscribedContacts, campaigns } =
|
||||
await contactBookService.getContactBookDetails(contactBook.id);
|
||||
|
||||
return {
|
||||
...contactBook,
|
||||
totalContacts,
|
||||
unsubscribedContacts,
|
||||
campaigns,
|
||||
};
|
||||
},
|
||||
),
|
||||
return {
|
||||
...contactBook,
|
||||
totalContacts,
|
||||
unsubscribedContacts,
|
||||
campaigns,
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
updateContactBook: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
contactBookId: z.string(),
|
||||
name: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
emoji: z.string().optional(),
|
||||
doubleOptInEnabled: z.boolean().optional(),
|
||||
doubleOptInFrom: z.string().nullable().optional(),
|
||||
doubleOptInSubject: z.string().optional(),
|
||||
doubleOptInContent: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { contactBook }, input }) => {
|
||||
const { contactBookId, ...data } = input;
|
||||
return contactBookService.updateContactBook(contactBook.id, data);
|
||||
}),
|
||||
updateContactBook: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
contactBookId: z.string(),
|
||||
name: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
emoji: z.string().optional(),
|
||||
doubleOptInEnabled: z.boolean().optional(),
|
||||
doubleOptInFrom: z.string().nullable().optional(),
|
||||
doubleOptInSubject: z.string().optional(),
|
||||
doubleOptInContent: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { contactBook }, input }) => {
|
||||
const { contactBookId, ...data } = input;
|
||||
return contactBookService.updateContactBook(contactBook.id, data);
|
||||
}),
|
||||
|
||||
deleteContactBook: contactBookProcedure
|
||||
.input(z.object({ contactBookId: z.string() }))
|
||||
.mutation(async ({ ctx: { contactBook }, input }) => {
|
||||
return contactBookService.deleteContactBook(contactBook.id);
|
||||
}),
|
||||
deleteContactBook: contactBookProcedure
|
||||
.input(z.object({ contactBookId: z.string() }))
|
||||
.mutation(async ({ ctx: { contactBook }, input }) => {
|
||||
return contactBookService.deleteContactBook(contactBook.id);
|
||||
}),
|
||||
|
||||
contacts: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
page: z.number().optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
search: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx: { db }, input }) => {
|
||||
const page = input.page || 1;
|
||||
const limit = 30;
|
||||
const offset = (page - 1) * limit;
|
||||
contacts: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
page: z.number().optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
search: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx: { db }, input }) => {
|
||||
const page = input.page || 1;
|
||||
const limit = 30;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
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 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 countP = db.contact.count({ where: whereConditions });
|
||||
const countP = db.contact.count({ where: whereConditions });
|
||||
|
||||
const contactsP = db.contact.findMany({
|
||||
where: whereConditions,
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
subscribed: true,
|
||||
createdAt: true,
|
||||
contactBookId: true,
|
||||
unsubscribeReason: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
const contactsP = db.contact.findMany({
|
||||
where: whereConditions,
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
subscribed: true,
|
||||
createdAt: true,
|
||||
contactBookId: true,
|
||||
unsubscribeReason: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
|
||||
const [contacts, count] = await Promise.all([contactsP, countP]);
|
||||
const [contacts, count] = await Promise.all([contactsP, countP]);
|
||||
|
||||
return { contacts, totalPage: Math.ceil(count / limit) };
|
||||
}),
|
||||
return { contacts, totalPage: Math.ceil(count / limit) };
|
||||
}),
|
||||
|
||||
addContacts: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
contacts: z
|
||||
.array(
|
||||
z.object({
|
||||
email: z.string(),
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.max(50000),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
return contactService.bulkAddContacts(
|
||||
contactBook.id,
|
||||
input.contacts,
|
||||
team.id,
|
||||
);
|
||||
}),
|
||||
addContacts: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
contacts: z
|
||||
.array(
|
||||
z.object({
|
||||
email: z.string(),
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.max(50000),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
return contactService.bulkAddContacts(
|
||||
contactBook.id,
|
||||
input.contacts,
|
||||
team.id,
|
||||
);
|
||||
}),
|
||||
|
||||
updateContact: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
contactId: z.string(),
|
||||
email: z.string().optional(),
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
const { contactId, ...contact } = input;
|
||||
const updatedContact = await contactService.updateContactInContactBook(
|
||||
contactId,
|
||||
contactBook.id,
|
||||
contact,
|
||||
team.id,
|
||||
);
|
||||
updateContact: contactBookProcedure
|
||||
.input(
|
||||
z.object({
|
||||
contactId: z.string(),
|
||||
email: z.string().optional(),
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
const { contactId, ...contact } = input;
|
||||
const updatedContact = await contactService.updateContactInContactBook(
|
||||
contactId,
|
||||
contactBook.id,
|
||||
contact,
|
||||
team.id,
|
||||
);
|
||||
|
||||
if (!updatedContact) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Contact not found",
|
||||
});
|
||||
}
|
||||
if (!updatedContact) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Contact not found",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedContact;
|
||||
}),
|
||||
return updatedContact;
|
||||
}),
|
||||
|
||||
deleteContact: contactBookProcedure
|
||||
.input(z.object({ contactId: z.string() }))
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
const deletedContact = await contactService.deleteContactInContactBook(
|
||||
input.contactId,
|
||||
contactBook.id,
|
||||
team.id,
|
||||
);
|
||||
deleteContact: contactBookProcedure
|
||||
.input(z.object({ contactId: z.string() }))
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
const deletedContact = await contactService.deleteContactInContactBook(
|
||||
input.contactId,
|
||||
contactBook.id,
|
||||
team.id,
|
||||
);
|
||||
|
||||
if (!deletedContact) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Contact not found",
|
||||
});
|
||||
}
|
||||
if (!deletedContact) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Contact not found",
|
||||
});
|
||||
}
|
||||
|
||||
return deletedContact;
|
||||
}),
|
||||
return deletedContact;
|
||||
}),
|
||||
|
||||
bulkDeleteContacts: contactBookProcedure
|
||||
.input(z.object({ contactIds: z.array(z.string()).min(1).max(1000) }))
|
||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||
const deletedContacts =
|
||||
await contactService.bulkDeleteContactsInContactBook(
|
||||
input.contactIds,
|
||||
contactBook.id,
|
||||
team.id,
|
||||
);
|
||||
|
||||
return { count: deletedContacts.length };
|
||||
}),
|
||||
|
||||
resendDoubleOptInConfirmation: contactBookProcedure
|
||||
.input(z.object({ contactId: z.string() }))
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||
import { deleteCampaign } from "~/server/service/campaign-service";
|
||||
import { campaignResponseSchema } from "~/server/public-api/schemas/campaign-schema";
|
||||
|
||||
const route = createRoute({
|
||||
method: "delete",
|
||||
path: "/v1/campaigns/{campaignId}",
|
||||
request: {
|
||||
params: z.object({
|
||||
campaignId: z
|
||||
.string()
|
||||
.min(1)
|
||||
.openapi({
|
||||
param: {
|
||||
name: "campaignId",
|
||||
in: "path",
|
||||
},
|
||||
example: "cmp_123",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Delete campaign",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: campaignResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function deleteCampaignHandle(app: PublicAPIApp) {
|
||||
app.openapi(route, async (c) => {
|
||||
const team = c.var.team;
|
||||
const campaignId = c.req.param("campaignId");
|
||||
|
||||
const campaign = await deleteCampaign(campaignId, team.id);
|
||||
return c.json(campaign);
|
||||
});
|
||||
}
|
||||
|
||||
export default deleteCampaignHandle;
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||
import {
|
||||
getCampaignForTeam,
|
||||
pauseCampaign as pauseCampaignService,
|
||||
pauseCampaign,
|
||||
} from "~/server/service/campaign-service";
|
||||
import { campaignResponseSchema } from "~/server/public-api/schemas/campaign-schema";
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
@@ -37,12 +35,12 @@ const route = createRoute({
|
||||
},
|
||||
});
|
||||
|
||||
function pauseCampaign(app: PublicAPIApp) {
|
||||
function pauseCampaignHandle(app: PublicAPIApp) {
|
||||
app.openapi(route, async (c) => {
|
||||
const team = c.var.team;
|
||||
const campaignId = c.req.param("campaignId");
|
||||
|
||||
await pauseCampaignService({
|
||||
await pauseCampaign({
|
||||
campaignId,
|
||||
teamId: team.id,
|
||||
});
|
||||
@@ -51,4 +49,4 @@ function pauseCampaign(app: PublicAPIApp) {
|
||||
});
|
||||
}
|
||||
|
||||
export default pauseCampaign;
|
||||
export default pauseCampaignHandle;
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||
import {
|
||||
getCampaignForTeam,
|
||||
resumeCampaign as resumeCampaignService,
|
||||
resumeCampaign,
|
||||
} from "~/server/service/campaign-service";
|
||||
import {
|
||||
campaignResponseSchema,
|
||||
parseScheduledAt,
|
||||
} from "~/server/public-api/schemas/campaign-schema";
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
@@ -40,17 +35,12 @@ const route = createRoute({
|
||||
},
|
||||
});
|
||||
|
||||
function resumeCampaign(app: PublicAPIApp) {
|
||||
function resumeCampaignHandle(app: PublicAPIApp) {
|
||||
app.openapi(route, async (c) => {
|
||||
const team = c.var.team;
|
||||
const campaignId = c.req.param("campaignId");
|
||||
|
||||
await resumeCampaignService({
|
||||
campaignId,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
await getCampaignForTeam({
|
||||
await resumeCampaign({
|
||||
campaignId,
|
||||
teamId: team.id,
|
||||
});
|
||||
@@ -59,4 +49,4 @@ function resumeCampaign(app: PublicAPIApp) {
|
||||
});
|
||||
}
|
||||
|
||||
export default resumeCampaign;
|
||||
export default resumeCampaignHandle;
|
||||
|
||||
@@ -3,11 +3,9 @@ import { PublicAPIApp } from "~/server/public-api/hono";
|
||||
import {
|
||||
campaignScheduleSchema,
|
||||
CampaignScheduleInput,
|
||||
campaignResponseSchema,
|
||||
parseScheduledAt,
|
||||
} from "~/server/public-api/schemas/campaign-schema";
|
||||
import {
|
||||
getCampaignForTeam,
|
||||
scheduleCampaign as scheduleCampaignService,
|
||||
} from "~/server/service/campaign-service";
|
||||
const route = createRoute({
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||
import { bulkAddContacts } from "~/server/service/contact-service";
|
||||
import { getContactBook } from "../../api-utils";
|
||||
|
||||
const contactSchema = z.object({
|
||||
email: z.string(),
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
properties: z.record(z.string()).optional(),
|
||||
subscribed: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const route = createRoute({
|
||||
method: "post",
|
||||
path: "/v1/contactBooks/{contactBookId}/contacts/bulk",
|
||||
request: {
|
||||
params: z.object({
|
||||
contactBookId: z
|
||||
.string()
|
||||
.min(3)
|
||||
.openapi({
|
||||
param: {
|
||||
name: "contactBookId",
|
||||
in: "path",
|
||||
},
|
||||
example: "cuiwqdj74rygf74",
|
||||
}),
|
||||
}),
|
||||
body: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.array(contactSchema).max(1000, {
|
||||
message:
|
||||
"Cannot add more than 1000 contacts in a single bulk request",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
message: z.string(),
|
||||
count: z.number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
description: "Bulk add contacts to a contact book",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function bulkAddContactsHandle(app: PublicAPIApp) {
|
||||
app.openapi(route, async (c) => {
|
||||
const team = c.var.team;
|
||||
|
||||
const contactBook = await getContactBook(c, team.id);
|
||||
|
||||
const result = await bulkAddContacts(
|
||||
contactBook.id,
|
||||
c.req.valid("json"),
|
||||
team.id,
|
||||
);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
}
|
||||
|
||||
export default bulkAddContactsHandle;
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createRoute, z } from "@hono/zod-openapi";
|
||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||
import { bulkDeleteContactsInContactBook } from "~/server/service/contact-service";
|
||||
import { getContactBook } from "../../api-utils";
|
||||
|
||||
const route = createRoute({
|
||||
method: "delete",
|
||||
path: "/v1/contactBooks/{contactBookId}/contacts/bulk",
|
||||
request: {
|
||||
params: z.object({
|
||||
contactBookId: z
|
||||
.string()
|
||||
.min(3)
|
||||
.openapi({
|
||||
param: {
|
||||
name: "contactBookId",
|
||||
in: "path",
|
||||
},
|
||||
example: "cuiwqdj74rygf74",
|
||||
}),
|
||||
}),
|
||||
body: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
contactIds: z.array(z.string()).min(1).max(1000, {
|
||||
message:
|
||||
"Cannot delete more than 1000 contacts in a single request",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
success: z.boolean(),
|
||||
count: z.number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
description: "Bulk delete contacts from a contact book",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function bulkDeleteContacts(app: PublicAPIApp) {
|
||||
app.openapi(route, async (c) => {
|
||||
const team = c.var.team;
|
||||
|
||||
const contactBook = await getContactBook(c, team.id);
|
||||
|
||||
const deletedContacts = await bulkDeleteContactsInContactBook(
|
||||
c.req.valid("json").contactIds,
|
||||
contactBook.id,
|
||||
team.id,
|
||||
);
|
||||
|
||||
return c.json({ success: true, count: deletedContacts.length });
|
||||
});
|
||||
}
|
||||
|
||||
export default bulkDeleteContacts;
|
||||
@@ -18,15 +18,18 @@ import deleteDomain from "./api/domains/delete-domain";
|
||||
import sendBatch from "./api/emails/batch-email";
|
||||
import createCampaign from "./api/campaigns/create-campaign";
|
||||
import getCampaign from "./api/campaigns/get-campaign";
|
||||
import deleteCampaignHandle from "./api/campaigns/delete-campaign";
|
||||
import getCampaigns from "./api/campaigns/get-campaigns";
|
||||
import scheduleCampaign from "./api/campaigns/schedule-campaign";
|
||||
import pauseCampaign from "./api/campaigns/pause-campaign";
|
||||
import resumeCampaign from "./api/campaigns/resume-campaign";
|
||||
import pauseCampaignHandle from "./api/campaigns/pause-campaign";
|
||||
import resumeCampaignHandle from "./api/campaigns/resume-campaign";
|
||||
import getContactBooks from "./api/contacts/get-contact-books";
|
||||
import createContactBook from "./api/contacts/create-contact-book";
|
||||
import getContactBook from "./api/contacts/get-contact-book";
|
||||
import updateContactBook from "./api/contacts/update-contact-book";
|
||||
import deleteContactBook from "./api/contacts/delete-contact-book";
|
||||
import bulkAddContactsHandle from "./api/contacts/bulk-add-contacts";
|
||||
import bulkDeleteContacts from "./api/contacts/bulk-delete-contacts";
|
||||
|
||||
export const app = getApp();
|
||||
|
||||
@@ -52,6 +55,8 @@ getContact(app);
|
||||
getContacts(app);
|
||||
upsertContact(app);
|
||||
deleteContact(app);
|
||||
bulkAddContactsHandle(app);
|
||||
bulkDeleteContacts(app);
|
||||
|
||||
/**Contact Book related APIs */
|
||||
getContactBooks(app);
|
||||
@@ -65,7 +70,8 @@ createCampaign(app);
|
||||
getCampaign(app);
|
||||
getCampaigns(app);
|
||||
scheduleCampaign(app);
|
||||
pauseCampaign(app);
|
||||
resumeCampaign(app);
|
||||
pauseCampaignHandle(app);
|
||||
resumeCampaignHandle(app);
|
||||
deleteCampaignHandle(app);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -669,7 +669,18 @@ export async function subscribeContact(id: string, hash: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteCampaign(id: string) {
|
||||
export async function deleteCampaign(id: string, teamId: number) {
|
||||
const existing = await db.campaign.findFirst({
|
||||
where: { id, teamId },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
throw new UnsendApiError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Campaign not found",
|
||||
});
|
||||
}
|
||||
|
||||
const campaign = await db.$transaction(async (tx) => {
|
||||
await tx.campaignEmail.deleteMany({
|
||||
where: { campaignId: id },
|
||||
|
||||
@@ -214,6 +214,38 @@ export async function deleteContactInContactBook(
|
||||
return deletedContact;
|
||||
}
|
||||
|
||||
export async function bulkDeleteContactsInContactBook(
|
||||
contactIds: string[],
|
||||
contactBookId: string,
|
||||
teamId?: number,
|
||||
) {
|
||||
const contacts = await db.contact.findMany({
|
||||
where: {
|
||||
id: { in: contactIds },
|
||||
contactBookId,
|
||||
},
|
||||
});
|
||||
|
||||
if (contacts.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await db.contact.deleteMany({
|
||||
where: {
|
||||
id: { in: contacts.map((c) => c.id) },
|
||||
contactBookId,
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
contacts.map((contact) =>
|
||||
emitContactEvent(contact, "contact.deleted", teamId),
|
||||
),
|
||||
);
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
export async function resendDoubleOptInConfirmationInContactBook(
|
||||
contactId: string,
|
||||
contactBookId: string,
|
||||
|
||||
Reference in New Issue
Block a user