diff --git a/apps/docs/api-reference/campaigns/get-campaigns.mdx b/apps/docs/api-reference/campaigns/get-campaigns.mdx new file mode 100644 index 00000000..daaeb870 --- /dev/null +++ b/apps/docs/api-reference/campaigns/get-campaigns.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v1/campaigns +--- diff --git a/apps/docs/api-reference/openapi.json b/apps/docs/api-reference/openapi.json index 3e6610d4..af5ffbd1 100644 --- a/apps/docs/api-reference/openapi.json +++ b/apps/docs/api-reference/openapi.json @@ -1358,6 +1358,112 @@ } }, "/v1/campaigns": { + "get": { + "parameters": [ + { + "schema": { "type": "string", "example": "1" }, + "required": false, + "name": "page", + "in": "query", + "description": "Page number for pagination (default: 1)" + }, + { + "schema": { + "type": "string", + "enum": [ + "DRAFT", + "SCHEDULED", + "SENDING", + "PAUSED", + "SENT", + "CANCELLED" + ], + "example": "DRAFT" + }, + "required": false, + "name": "status", + "in": "query", + "description": "Filter campaigns by status" + }, + { + "schema": { "type": "string", "example": "newsletter" }, + "required": false, + "name": "search", + "in": "query", + "description": "Search campaigns by name or subject" + } + ], + "responses": { + "200": { + "description": "Get list of campaigns", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "campaigns": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "from": { "type": "string" }, + "subject": { "type": "string" }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "SCHEDULED", + "SENDING", + "PAUSED", + "SENT", + "CANCELLED" + ] + }, + "scheduledAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "total": { "type": "integer" }, + "sent": { "type": "integer" }, + "delivered": { "type": "integer" }, + "unsubscribed": { "type": "integer" } + }, + "required": [ + "id", + "name", + "from", + "subject", + "createdAt", + "updatedAt", + "status", + "scheduledAt", + "total", + "sent", + "delivered", + "unsubscribed" + ] + } + }, + "totalPage": { "type": "integer" } + }, + "required": ["campaigns", "totalPage"] + } + } + } + } + } + }, "post": { "requestBody": { "required": true, diff --git a/apps/docs/docs.json b/apps/docs/docs.json index eac7cf29..a30a757f 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -1,162 +1,163 @@ { - "$schema": "https://mintlify.com/docs.json", - "theme": "maple", - "name": "useSend", - "colors": { - "primary": "#21453D", - "light": "#E6FAF5", - "dark": "#21453D" - }, - "background": { - "color": { - "light": "#F5F5F5", - "dark": "#181825" - } - }, - "fonts": { - "family": "IBM Plex Mono" - }, - "favicon": "/favicon.svg", - "navigation": { - "tabs": [ - { - "tab": "Documentation", - "groups": [ - { - "group": "Getting Started", - "pages": [ - "introduction", - "get-started/nodejs", - "get-started/python", - "get-started/local", - "get-started/smtp" - ] - }, - { - "group": "Self Hosting", - "pages": ["self-hosting/overview", "self-hosting/railway"] - }, - { - "group": "Guides", - "pages": ["guides/use-with-react-email"] - }, - { - "group": "Community SDKs", - "pages": ["community-sdk/python", "community-sdk/go"] - } - ] - }, - { - "tab": "API Reference", - "groups": [ - { - "group": "API Reference", - "pages": ["api-reference/introduction"] - }, - { - "group": "Emails", - "pages": [ - "api-reference/emails/get-email", - "api-reference/emails/list-emails", - "api-reference/emails/send-email", - "api-reference/emails/batch-email", - "api-reference/emails/update-schedule", - "api-reference/emails/cancel-schedule" - ] - }, - { - "group": "Contacts", - "pages": [ - "api-reference/contacts/get-contact", - "api-reference/contacts/get-contacts", - "api-reference/contacts/create-contact", - "api-reference/contacts/update-contact", - "api-reference/contacts/upsert-contact", - "api-reference/contacts/delete-contact" - ] - }, - { - "group": "Domains", - "pages": [ - "api-reference/domains/get-domain", - "api-reference/domains/list-domains", - "api-reference/domains/create-domain", - "api-reference/domains/verify-domain", - "api-reference/domains/delete-domain" - ] - }, - { - "group": "Campaigns", - "pages": [ - "api-reference/campaigns/create-campaign", - "api-reference/campaigns/get-campaign", - "api-reference/campaigns/schedule-campaign", - "api-reference/campaigns/pause-campaign", - "api-reference/campaigns/resume-campaign" - ] - } - ] - }, - { - "tab": "Changelog", - "groups": [ - { - "group": "Updates", - "pages": ["changelog"] - } - ] - } - ], - "global": { - "anchors": [ - { - "anchor": "GitHub", - "href": "https://github.com/usesend/usesend", - "icon": "github" - }, - { - "anchor": "Community", - "href": "https://discord.gg/BU8n8pJv8S", - "icon": "discord" - } - ] - } - }, - "logo": { - "light": "/logo/logo-wordmark.svg", - "dark": "/logo/logo-wordmark-dark.svg" - }, - "api": { - "playground": { - "display": "interactive" - }, - "mdx": { - "server": "https://mintlify.com/api", - "auth": { - "method": "bearer" - } - } - }, - "navbar": { - "links": [ - { - "label": "Support", - "href": "mailto:hey@usesend.com" - } - ], - "primary": { - "type": "button", - "label": "Dashboard", - "href": "https://app.usesend.com" - } - }, - "footer": { - "socials": { - "x": "https://x.com/useSend_com", - "github": "https://github.com/usesend" - } - }, - "contextual": { - "options": ["copy", "view", "chatgpt", "claude", "perplexity"] - } + "$schema": "https://mintlify.com/docs.json", + "theme": "maple", + "name": "useSend", + "colors": { + "primary": "#21453D", + "light": "#E6FAF5", + "dark": "#21453D" + }, + "background": { + "color": { + "light": "#F5F5F5", + "dark": "#181825" + } + }, + "fonts": { + "family": "IBM Plex Mono" + }, + "favicon": "/favicon.svg", + "navigation": { + "tabs": [ + { + "tab": "Documentation", + "groups": [ + { + "group": "Getting Started", + "pages": [ + "introduction", + "get-started/nodejs", + "get-started/python", + "get-started/local", + "get-started/smtp" + ] + }, + { + "group": "Self Hosting", + "pages": ["self-hosting/overview", "self-hosting/railway"] + }, + { + "group": "Guides", + "pages": ["guides/use-with-react-email"] + }, + { + "group": "Community SDKs", + "pages": ["community-sdk/python", "community-sdk/go"] + } + ] + }, + { + "tab": "API Reference", + "groups": [ + { + "group": "API Reference", + "pages": ["api-reference/introduction"] + }, + { + "group": "Emails", + "pages": [ + "api-reference/emails/get-email", + "api-reference/emails/list-emails", + "api-reference/emails/send-email", + "api-reference/emails/batch-email", + "api-reference/emails/update-schedule", + "api-reference/emails/cancel-schedule" + ] + }, + { + "group": "Contacts", + "pages": [ + "api-reference/contacts/get-contact", + "api-reference/contacts/get-contacts", + "api-reference/contacts/create-contact", + "api-reference/contacts/update-contact", + "api-reference/contacts/upsert-contact", + "api-reference/contacts/delete-contact" + ] + }, + { + "group": "Domains", + "pages": [ + "api-reference/domains/get-domain", + "api-reference/domains/list-domains", + "api-reference/domains/create-domain", + "api-reference/domains/verify-domain", + "api-reference/domains/delete-domain" + ] + }, + { + "group": "Campaigns", + "pages": [ + "api-reference/campaigns/create-campaign", + "api-reference/campaigns/get-campaigns", + "api-reference/campaigns/get-campaign", + "api-reference/campaigns/schedule-campaign", + "api-reference/campaigns/pause-campaign", + "api-reference/campaigns/resume-campaign" + ] + } + ] + }, + { + "tab": "Changelog", + "groups": [ + { + "group": "Updates", + "pages": ["changelog"] + } + ] + } + ], + "global": { + "anchors": [ + { + "anchor": "GitHub", + "href": "https://github.com/usesend/usesend", + "icon": "github" + }, + { + "anchor": "Community", + "href": "https://discord.gg/BU8n8pJv8S", + "icon": "discord" + } + ] + } + }, + "logo": { + "light": "/logo/logo-wordmark.svg", + "dark": "/logo/logo-wordmark-dark.svg" + }, + "api": { + "playground": { + "display": "interactive" + }, + "mdx": { + "server": "https://mintlify.com/api", + "auth": { + "method": "bearer" + } + } + }, + "navbar": { + "links": [ + { + "label": "Support", + "href": "mailto:hey@usesend.com" + } + ], + "primary": { + "type": "button", + "label": "Dashboard", + "href": "https://app.usesend.com" + } + }, + "footer": { + "socials": { + "x": "https://x.com/useSend_com", + "github": "https://github.com/usesend" + } + }, + "contextual": { + "options": ["copy", "view", "chatgpt", "claude", "perplexity"] + } } diff --git a/apps/web/src/server/public-api/api/campaigns/get-campaigns.ts b/apps/web/src/server/public-api/api/campaigns/get-campaigns.ts new file mode 100644 index 00000000..af973dda --- /dev/null +++ b/apps/web/src/server/public-api/api/campaigns/get-campaigns.ts @@ -0,0 +1,126 @@ +import { createRoute, z } from "@hono/zod-openapi"; +import { CampaignStatus, Prisma } from "@prisma/client"; +import { PublicAPIApp } from "~/server/public-api/hono"; +import { db } from "~/server/db"; + +const statuses = Object.values(CampaignStatus) as [CampaignStatus]; + +const route = createRoute({ + method: "get", + path: "/v1/campaigns", + request: { + query: z.object({ + page: z.string().optional().openapi({ + description: "Page number for pagination (default: 1)", + example: "1", + }), + status: z.enum(statuses).optional().openapi({ + description: "Filter campaigns by status", + example: "DRAFT", + }), + search: z.string().optional().openapi({ + description: "Search campaigns by name or subject", + example: "newsletter", + }), + }), + }, + responses: { + 200: { + description: "Get list of campaigns", + content: { + "application/json": { + schema: z.object({ + campaigns: z.array( + z.object({ + id: z.string(), + name: z.string(), + from: z.string(), + subject: z.string(), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), + status: z.string(), + scheduledAt: z.string().datetime().nullable(), + total: z.number().int(), + sent: z.number().int(), + delivered: z.number().int(), + unsubscribed: z.number().int(), + }) + ), + totalPage: z.number().int(), + }), + }, + }, + }, + }, +}); + +function getCampaigns(app: PublicAPIApp) { + app.openapi(route, async (c) => { + const team = c.var.team; + const pageParam = c.req.query("page"); + const statusParam = c.req.query("status") as + | Prisma.EnumCampaignStatusFilter<"Campaign"> + | undefined; + const searchParam = c.req.query("search"); + + const page = pageParam ? Number(pageParam) : 1; + const limit = 30; + const offset = (page - 1) * limit; + + const whereConditions: Prisma.CampaignWhereInput = { + teamId: team.id, + }; + + if (statusParam) { + whereConditions.status = statusParam; + } + + if (searchParam) { + whereConditions.OR = [ + { + name: { + contains: searchParam, + mode: "insensitive", + }, + }, + { + subject: { + contains: searchParam, + mode: "insensitive", + }, + }, + ]; + } + + const countP = db.campaign.count({ where: whereConditions }); + + const campaignsP = db.campaign.findMany({ + where: whereConditions, + select: { + id: true, + name: true, + from: true, + subject: true, + createdAt: true, + updatedAt: true, + status: true, + scheduledAt: true, + total: true, + sent: true, + delivered: true, + unsubscribed: true, + }, + orderBy: { + createdAt: "desc", + }, + skip: offset, + take: limit, + }); + + const [campaigns, count] = await Promise.all([campaignsP, countP]); + + return c.json({ campaigns, totalPage: Math.ceil(count / limit) }); + }); +} + +export default getCampaigns; diff --git a/apps/web/src/server/public-api/index.ts b/apps/web/src/server/public-api/index.ts index 96eacf54..9f4afdda 100644 --- a/apps/web/src/server/public-api/index.ts +++ b/apps/web/src/server/public-api/index.ts @@ -18,6 +18,7 @@ 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 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"; @@ -50,6 +51,7 @@ deleteContact(app); /**Campaign related APIs */ createCampaign(app); getCampaign(app); +getCampaigns(app); scheduleCampaign(app); pauseCampaign(app); resumeCampaign(app);