From b9d9037ec7f2b4f0b921ea15fe801f9225d11c1e Mon Sep 17 00:00:00 2001 From: KM Koushik Date: Thu, 6 Feb 2025 00:27:03 +1100 Subject: [PATCH] get contacts from emailid,ids (#99) --- .../api-reference/contacts/get-contacts.mdx | 3 + apps/docs/api-reference/openapi.json | 259 ++++++++++++------ apps/docs/mint.json | 1 + .../public-api/api/contacts/get-contacts.ts | 82 ++++++ apps/web/src/server/public-api/index.ts | 6 + 5 files changed, 270 insertions(+), 81 deletions(-) create mode 100644 apps/docs/api-reference/contacts/get-contacts.mdx create mode 100644 apps/web/src/server/public-api/api/contacts/get-contacts.ts diff --git a/apps/docs/api-reference/contacts/get-contacts.mdx b/apps/docs/api-reference/contacts/get-contacts.mdx new file mode 100644 index 0000000..d1a66ba --- /dev/null +++ b/apps/docs/api-reference/contacts/get-contacts.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v1/contactBooks/{contactBookId}/contacts/ +--- diff --git a/apps/docs/api-reference/openapi.json b/apps/docs/api-reference/openapi.json index 82cfa59..137804a 100644 --- a/apps/docs/api-reference/openapi.json +++ b/apps/docs/api-reference/openapi.json @@ -535,9 +535,178 @@ } } } + }, + "get": { + "parameters": [ + { + "schema": { + "type": "string", + "example": "cuiwqdj74rygf74" + }, + "required": true, + "name": "contactBookId", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "emails", + "in": "query" + }, + { + "schema": { + "type": "number" + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "number" + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "ids", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Retrieve multiple contacts", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "firstName": { + "type": "string", + "nullable": true + }, + "lastName": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "subscribed": { + "type": "boolean", + "default": true + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "contactBookId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "properties", + "contactBookId", + "createdAt", + "updatedAt" + ] + } + } + } + } + } + } } }, "/v1/contactBooks/{contactBookId}/contacts/{contactId}": { + "patch": { + "parameters": [ + { + "schema": { + "type": "string", + "example": "cuiwqdj74rygf74" + }, + "required": true, + "name": "contactBookId", + "in": "path" + }, + { + "schema": { + "type": "string", + "example": "cuiwqdj74rygf74" + }, + "required": true, + "name": "contactId", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "subscribed": { + "type": "boolean" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Retrieve the user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contactId": { + "type": "string" + } + } + } + } + } + } + } + }, "get": { "parameters": [ { @@ -615,93 +784,17 @@ } } }, - "patch": { - "parameters": [ - { - "schema": { - "type": "string", - "example": "cuiwqdj74rygf74" - }, - "required": true, - "name": "contactBookId", - "in": "path" - }, - { - "schema": { - "type": "string", - "example": "cuiwqdj74rygf74" - }, - "required": true, - "name": "contactId", - "in": "path" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "properties": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "subscribed": { - "type": "boolean" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Retrieve the user", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "contactId": { - "type": "string" - } - } - } - } - } - } - } - }, "put": { - "summary": "Upsert a contact", - "description": "Create a new contact or update an existing one based on the contactId", "parameters": [ { "schema": { "type": "string", + "minLength": 3, "example": "cuiwqdj74rygf74" }, "required": true, "name": "contactBookId", "in": "path" - }, - { - "schema": { - "type": "string", - "example": "cuiwqdj74rygf74" - }, - "required": true, - "name": "contactId", - "in": "path" } ], "requestBody": { @@ -748,7 +841,10 @@ "contactId": { "type": "string" } - } + }, + "required": [ + "contactId" + ] } } } @@ -756,8 +852,6 @@ } }, "delete": { - "summary": "Delete a contact", - "description": "Delete a contact from a contact book", "parameters": [ { "schema": { @@ -789,7 +883,10 @@ "success": { "type": "boolean" } - } + }, + "required": [ + "success" + ] } } } diff --git a/apps/docs/mint.json b/apps/docs/mint.json index 8ea170f..f88743d 100644 --- a/apps/docs/mint.json +++ b/apps/docs/mint.json @@ -85,6 +85,7 @@ "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", diff --git a/apps/web/src/server/public-api/api/contacts/get-contacts.ts b/apps/web/src/server/public-api/api/contacts/get-contacts.ts new file mode 100644 index 0000000..59278d2 --- /dev/null +++ b/apps/web/src/server/public-api/api/contacts/get-contacts.ts @@ -0,0 +1,82 @@ +import { createRoute, z } from "@hono/zod-openapi"; +import { PublicAPIApp } from "~/server/public-api/hono"; +import { getTeamFromToken } from "~/server/public-api/auth"; +import { db } from "~/server/db"; +import { UnsendApiError } from "../../api-error"; +import { getContactBook } from "../../api-utils"; + +const route = createRoute({ + method: "get", + path: "/v1/contactBooks/{contactBookId}/contacts", + request: { + params: z.object({ + contactBookId: z.string().openapi({ + param: { + name: "contactBookId", + in: "path", + }, + example: "cuiwqdj74rygf74", + }), + }), + query: z.object({ + emails: z.string().optional(), + page: z.number().optional(), + limit: z.number().optional(), + ids: z.string().optional(), + }), + }, + responses: { + 200: { + content: { + "application/json": { + schema: z.array( + z.object({ + id: z.string(), + firstName: z.string().optional().nullable(), + lastName: z.string().optional().nullable(), + email: z.string(), + subscribed: z.boolean().default(true), + properties: z.record(z.string()), + contactBookId: z.string(), + createdAt: z.string(), + updatedAt: z.string(), + }) + ), + }, + }, + description: "Retrieve multiple contacts", + }, + }, +}); + +function getContacts(app: PublicAPIApp) { + app.openapi(route, async (c) => { + const team = await getTeamFromToken(c); + + await getContactBook(c, team.id); + + const contactIds = c.req.query("ids")?.split(","); + const emails = c.req.query("emails")?.split(","); + const page = c.req.query("page") ? Number(c.req.query("page")) : 1; + const limit = c.req.query("limit") ? Number(c.req.query("limit")) : 5000; + + const contacts = await db.contact.findMany({ + where: { + id: { in: contactIds }, + email: { in: emails }, + }, + skip: (page - 1) * limit, + take: limit, + }); + + // Ensure properties is a Record + const sanitizedContacts = contacts.map((contact) => ({ + ...contact, + properties: contact.properties as Record, + })); + + return c.json(sanitizedContacts); + }); +} + +export default getContacts; diff --git a/apps/web/src/server/public-api/index.ts b/apps/web/src/server/public-api/index.ts index 0034b91..b982aa5 100644 --- a/apps/web/src/server/public-api/index.ts +++ b/apps/web/src/server/public-api/index.ts @@ -7,6 +7,9 @@ import updateContactInfo from "./api/contacts/update-contact"; import getContact from "./api/contacts/get-contact"; import updateEmailScheduledAt from "./api/emails/update-email"; import cancelScheduledEmail from "./api/emails/cancel-email"; +import getContacts from "./api/contacts/get-contacts"; +import upsertContact from "./api/contacts/upsert-contact"; +import deleteContact from "./api/contacts/delete-contact"; export const app = getApp(); @@ -23,5 +26,8 @@ cancelScheduledEmail(app); addContact(app); updateContactInfo(app); getContact(app); +getContacts(app); +upsertContact(app); +deleteContact(app); export default app;