diff --git a/apps/web/package.json b/apps/web/package.json index 06bcc50..b7e18c4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,6 +19,9 @@ "@auth/prisma-adapter": "^1.4.0", "@aws-sdk/client-sesv2": "^3.535.0", "@aws-sdk/client-sns": "^3.540.0", + "@hono/node-server": "^1.9.1", + "@hono/swagger-ui": "^0.2.1", + "@hono/zod-openapi": "^0.10.0", "@prisma/client": "^5.11.0", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-query": "^5.25.0", @@ -28,6 +31,7 @@ "@trpc/server": "next", "@unsend/ui": "workspace:*", "date-fns": "^3.6.0", + "hono": "^4.2.2", "install": "^0.13.0", "lucide-react": "^0.359.0", "next": "^14.1.3", diff --git a/apps/web/src/app/api/v1/[[...route]]/route.ts b/apps/web/src/app/api/v1/[[...route]]/route.ts new file mode 100644 index 0000000..afc0f92 --- /dev/null +++ b/apps/web/src/app/api/v1/[[...route]]/route.ts @@ -0,0 +1,7 @@ +import { handle } from "hono/vercel"; +import { app } from "~/server/public-api"; + +export const GET = handle(app); +export const POST = handle(app); +export const PUT = handle(app); +export const DELETE = handle(app); diff --git a/apps/web/src/lib/zod/domain-schema.ts b/apps/web/src/lib/zod/domain-schema.ts new file mode 100644 index 0000000..e18c6dc --- /dev/null +++ b/apps/web/src/lib/zod/domain-schema.ts @@ -0,0 +1,31 @@ +import { DomainStatus } from "@prisma/client"; +import { z } from "zod"; + +export const DomainStatusSchema = z.nativeEnum(DomainStatus); + +export const DomainSchema = z.object({ + id: z.number().openapi({ description: "The ID of the domain", example: 1 }), + name: z + .string() + .openapi({ description: "The name of the domain", example: "example.com" }), + teamId: z.number().openapi({ description: "The ID of the team", example: 1 }), + status: DomainStatusSchema, + region: z.string().default("us-east-1"), + clickTracking: z.boolean().default(false), + openTracking: z.boolean().default(false), + publicKey: z.string(), + dkimStatus: z.string().optional().nullish(), + spfDetails: z.string().optional().nullish(), + createdAt: z.string(), + updatedAt: z.string(), +}); + +export const CreateDomainSchema = DomainSchema.omit({ + id: true, + createdAt: true, + updatedAt: true, + teamId: true, + publicKey: true, + dkimStatus: true, + spfDetails: true, +}); diff --git a/apps/web/src/server/public-api/get_domains.ts b/apps/web/src/server/public-api/get_domains.ts new file mode 100644 index 0000000..d6f46e1 --- /dev/null +++ b/apps/web/src/server/public-api/get_domains.ts @@ -0,0 +1,29 @@ +import { createRoute, z } from "@hono/zod-openapi"; +import { DomainSchema } from "~/lib/zod/domain-schema"; +import { PublicAPIApp } from "./hono"; +import { db } from "../db"; + +const route = createRoute({ + method: "get", + path: "/domains", + responses: { + 200: { + content: { + "application/json": { + schema: z.array(DomainSchema), + }, + }, + description: "Retrieve the user", + }, + }, +}); + +function getDomains(app: PublicAPIApp) { + app.openapi(route, async (c) => { + const domains = await db.domain.findMany({}); + + return c.json(domains); + }); +} + +export default getDomains; diff --git a/apps/web/src/server/public-api/hono.ts b/apps/web/src/server/public-api/hono.ts new file mode 100644 index 0000000..36c5ff0 --- /dev/null +++ b/apps/web/src/server/public-api/hono.ts @@ -0,0 +1,7 @@ +import { OpenAPIHono } from "@hono/zod-openapi"; + +export function getApp() { + return new OpenAPIHono().basePath("/api/v1"); +} + +export type PublicAPIApp = ReturnType; diff --git a/apps/web/src/server/public-api/index.ts b/apps/web/src/server/public-api/index.ts new file mode 100644 index 0000000..b4a8f10 --- /dev/null +++ b/apps/web/src/server/public-api/index.ts @@ -0,0 +1,22 @@ +import { swaggerUI } from "@hono/swagger-ui"; + +import { getApp } from "./hono"; +import getDomains from "./get_domains"; + +export const app = getApp(); + +getDomains(app); + +// The OpenAPI documentation will be available at /doc +app.doc("/doc", { + openapi: "3.0.0", + info: { + version: "1.0.0", + title: "My API", + }, + servers: [{ url: "/api/v1" }], +}); + +app.get("/ui", swaggerUI({ url: "/api/v1/doc" })); + +export default app; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cac2c23..467fc2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,15 @@ importers: '@aws-sdk/client-sns': specifier: ^3.540.0 version: 3.540.0 + '@hono/node-server': + specifier: ^1.9.1 + version: 1.9.1 + '@hono/swagger-ui': + specifier: ^0.2.1 + version: 0.2.1(hono@4.2.2) + '@hono/zod-openapi': + specifier: ^0.10.0 + version: 0.10.0(hono@4.2.2)(zod@3.22.4) '@prisma/client': specifier: ^5.11.0 version: 5.11.0(prisma@5.11.0) @@ -118,6 +127,9 @@ importers: date-fns: specifier: ^3.6.0 version: 3.6.0 + hono: + specifier: ^4.2.2 + version: 4.2.2 install: specifier: ^0.13.0 version: 0.13.0 @@ -350,6 +362,15 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true + /@asteasolutions/zod-to-openapi@7.0.0(zod@3.22.4): + resolution: {integrity: sha512-rJRKHD2m6nUb/9ZheeN8nqOURX24WTzY8Sex1ZKT0Kpx+xfpRcD0fTD6vEeXNHGaDGxzu65Jj/jb2x6nLTjcMw==} + peerDependencies: + zod: ^3.20.2 + dependencies: + openapi3-ts: 4.3.1 + zod: 3.22.4 + dev: false + /@auth/core@0.28.0: resolution: {integrity: sha512-/fh/tb/L4NMSYcyPoo4Imn8vN6MskcVfgESF8/ndgtI4fhD/7u7i5fTVzWgNRZ4ebIEGHNDbWFRxaTu1NtQgvA==} peerDependencies: @@ -1444,6 +1465,42 @@ packages: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: false + /@hono/node-server@1.9.1: + resolution: {integrity: sha512-XBru0xbtRlTZJyAiFJLn7XDKbCVXBaRhVQAQhB9TwND2gwj8jf9SDWIj/7VxVtNAjURJf7Ofcz58DRA6DPYiWA==} + engines: {node: '>=18.14.1'} + dev: false + + /@hono/swagger-ui@0.2.1(hono@4.2.2): + resolution: {integrity: sha512-wBxVMRe3/v8xH4o6icmwztiIq0DG0s7+jHVMHVUAoFFCWEQNL2iskMmQtrhSDtsFmBZUeUFQUaaJ6Ir6DOmHLA==} + peerDependencies: + hono: '*' + dependencies: + hono: 4.2.2 + dev: false + + /@hono/zod-openapi@0.10.0(hono@4.2.2)(zod@3.22.4): + resolution: {integrity: sha512-XvUtRMI5IugLSkdadYi349k1I40RntfeDqAcIskQ5dXqD+7ZEn3PxGX/jntyHXcbTLLotfzRTXQi0eh4Bb/LWg==} + engines: {node: '>=16.0.0'} + peerDependencies: + hono: '>=3.11.3' + zod: 3.* + dependencies: + '@asteasolutions/zod-to-openapi': 7.0.0(zod@3.22.4) + '@hono/zod-validator': 0.2.1(hono@4.2.2)(zod@3.22.4) + hono: 4.2.2 + zod: 3.22.4 + dev: false + + /@hono/zod-validator@0.2.1(hono@4.2.2)(zod@3.22.4): + resolution: {integrity: sha512-HFoxln7Q6JsE64qz2WBS28SD33UB2alp3aRKmcWnNLDzEL1BLsWfbdX6e1HIiUprHYTIXf5y7ax8eYidKUwyaA==} + peerDependencies: + hono: '>=3.9.0' + zod: ^3.19.1 + dependencies: + hono: 4.2.2 + zod: 3.22.4 + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -4076,7 +4133,7 @@ packages: enhanced-resolve: 5.16.0 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -4888,6 +4945,11 @@ packages: dependencies: function-bind: 1.1.2 + /hono@4.2.2: + resolution: {integrity: sha512-mDmjBHF6uBNN3TASdAbDCFsN9FLbrlgXyFZkhLEkU7hUgk0+T9hcsUrL/nho4qV+Xk0RDHx7gop4Q1gelZZVRw==} + engines: {node: '>=16.0.0'} + dev: false + /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true @@ -5617,6 +5679,12 @@ packages: wrappy: 1.0.2 dev: true + /openapi3-ts@4.3.1: + resolution: {integrity: sha512-ha/kTOLhMQL7MvS9Abu/cpCXx5qwHQ++88YkUzn1CGfmM8JvCOG/4ZE6tRsexgXRFaoJrcwLyf81H2Y/CXALtA==} + dependencies: + yaml: 2.4.1 + dev: false + /openid-client@5.6.5: resolution: {integrity: sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==} dependencies: