diff --git a/apps/web/src/pages/api/auth/[...all].ts b/apps/web/src/pages/api/auth/[...all].ts index e384c9c2..f62fc73a 100644 --- a/apps/web/src/pages/api/auth/[...all].ts +++ b/apps/web/src/pages/api/auth/[...all].ts @@ -8,7 +8,3 @@ export const config = { api: { bodyParser: false } }; const auth = initAuth(createDrizzleClient()); export default toNodeHandler(auth.handler); - -// export const runtime = "edge"; -// export const preferredRegion = "lhr1"; -// export const dynamic = "force-dynamic"; diff --git a/apps/web/src/pages/api/auth/confirm.ts b/apps/web/src/pages/api/auth/confirm.ts index c9f4144a..e5b134a0 100644 --- a/apps/web/src/pages/api/auth/confirm.ts +++ b/apps/web/src/pages/api/auth/confirm.ts @@ -1,5 +1,5 @@ // import type { EmailOtpType } from "@supabase/supabase-js"; -// import type { NextRequest } from "next/server"; +// import type { NextApiRequest } from "next"; // import { NextResponse } from "next/server"; // import { createDrizzleClient } from "@kan/db/client"; @@ -8,7 +8,7 @@ // import { stripe } from "@kan/stripe"; // import { createNextApiClient } from "@kan/"; -// export default async function handler(req: NextRequest) { +// export default async function handler(req: NextApiRequest) { // if (req.method !== "GET") { // return new NextResponse(null, { // status: 405, @@ -96,7 +96,3 @@ // return redirectResponse; // } - -// export const runtime = "edge"; -// export const preferredRegion = "lhr1"; -// export const dynamic = "force-dynamic"; diff --git a/apps/web/src/pages/api/stripe/create_billing_session.ts b/apps/web/src/pages/api/stripe/create_billing_session.ts index 409815cc..3de83f47 100644 --- a/apps/web/src/pages/api/stripe/create_billing_session.ts +++ b/apps/web/src/pages/api/stripe/create_billing_session.ts @@ -1,29 +1,23 @@ -import type { NextRequest } from "next/server"; +import type { NextApiRequest, NextApiResponse } from "next"; import { createNextApiContext } from "@kan/api/trpc"; import { createStripeClient } from "@kan/stripe"; -export default async function handler(req: NextRequest) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { const stripe = createStripeClient(); if (req.method !== "POST") { - return new Response(JSON.stringify({ error: "Method not allowed" }), { - status: 405, - headers: { "Content-Type": "application/json" }, - }); + return res.status(405).json({ error: "Method not allowed" }); } try { const { user } = await createNextApiContext(req); if (!user?.stripeCustomerId) { - return new Response( - JSON.stringify({ error: "No billing account found" }), - { - status: 404, - headers: { "Content-Type": "application/json" }, - }, - ); + return res.status(404).json({ error: "No billing account found" }); } const session = await stripe.billingPortal.sessions.create({ @@ -31,22 +25,9 @@ export default async function handler(req: NextRequest) { return_url: `${process.env.WEBSITE_URL}/settings`, }); - return new Response(JSON.stringify({ url: session.url }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); + return res.status(200).json({ url: session.url }); } catch (error) { console.error("Error:", error); - return new Response( - JSON.stringify({ error: "Error creating portal session" }), - { - status: 500, - headers: { "Content-Type": "application/json" }, - }, - ); + return res.status(500).json({ error: "Error creating portal session" }); } } - -export const runtime = "edge"; -export const preferredRegion = "lhr1"; -export const dynamic = "force-dynamic"; diff --git a/apps/web/src/pages/api/stripe/create_checkout_session.ts b/apps/web/src/pages/api/stripe/create_checkout_session.ts index 9d46e7d3..6a3f700d 100644 --- a/apps/web/src/pages/api/stripe/create_checkout_session.ts +++ b/apps/web/src/pages/api/stripe/create_checkout_session.ts @@ -1,4 +1,4 @@ -import type { NextRequest } from "next/server"; +import type { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; import { createNextApiContext } from "@kan/api/trpc"; @@ -19,37 +19,28 @@ interface CheckoutSessionRequest { stripeCustomerId: string; } -export default async function handler(req: NextRequest) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { const stripe = createStripeClient(); if (req.method !== "POST") { - return new Response(JSON.stringify({ error: "Method not allowed" }), { - status: 405, - headers: { "Content-Type": "application/json" }, - }); + return res.status(405).json({ error: "Method not allowed" }); } try { const { user, db } = await createNextApiContext(req); if (!user) { - return new Response(JSON.stringify({ error: "User not found" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return res.status(404).json({ error: "User not found" }); } - const body = (await req.json()) as CheckoutSessionRequest; + const body = req.body as CheckoutSessionRequest; const { successUrl, cancelUrl, slug, workspacePublicId } = body; if (!successUrl || !cancelUrl || !slug || !workspacePublicId) { - return new Response( - JSON.stringify({ error: "Missing required fields" }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, - ); + return res.status(400).json({ error: "Missing required fields" }); } const slugResult = workspaceSlugSchema.safeParse(slug); @@ -91,22 +82,9 @@ export default async function handler(req: NextRequest) { }, }); - return new Response(JSON.stringify({ url: session.url }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); + return res.status(200).json({ url: session.url }); } catch (error) { console.error("Error:", error); - return new Response( - JSON.stringify({ error: "Error creating checkout session" }), - { - status: 500, - headers: { "Content-Type": "application/json" }, - }, - ); + return res.status(500).json({ error: "Error creating checkout session" }); } } - -export const runtime = "edge"; -export const preferredRegion = "lhr1"; -export const dynamic = "force-dynamic"; diff --git a/apps/web/src/pages/api/stripe/webhook.ts b/apps/web/src/pages/api/stripe/webhook.ts index 0b138f6a..32eb05b8 100644 --- a/apps/web/src/pages/api/stripe/webhook.ts +++ b/apps/web/src/pages/api/stripe/webhook.ts @@ -1,4 +1,4 @@ -import type { NextRequest } from "next/server"; +import type { NextApiRequest, NextApiResponse } from "next"; import { Stripe } from "stripe"; import { createNextApiContext } from "@kan/api/trpc"; @@ -7,25 +7,24 @@ import { createStripeClient } from "@kan/stripe"; export const webCrypto = Stripe.createSubtleCryptoProvider(); -export default async function handler(req: NextRequest) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { const stripe = createStripeClient(); if (req.method !== "POST") { - return new Response(JSON.stringify({ message: "Method not allowed" }), { - status: 405, - }); + return res.status(405).json({ message: "Method not allowed" }); } - const sig = req.headers.get("stripe-signature"); + const sig = req.headers["stripe-signature"]; if (!sig) { - return new Response(JSON.stringify({ message: "No signature found" }), { - status: 400, - }); + return res.status(400).json({ message: "No signature found" }); } try { - const body = await req.text(); + const body = req.body; const event = await stripe.webhooks.constructEventAsync( body, @@ -55,15 +54,9 @@ export default async function handler(req: NextRequest) { console.log(`Unhandled event type: ${event.type}`); } - return new Response(JSON.stringify({ received: true }), { status: 200 }); + return res.status(200).json({ received: true }); } catch (err) { console.error("Webhook error:", err); - return new Response(JSON.stringify({ message: "Webhook handler failed" }), { - status: 400, - }); + return res.status(400).json({ message: "Webhook handler failed" }); } } - -export const runtime = "edge"; -export const preferredRegion = "lhr1"; -export const dynamic = "force-dynamic"; diff --git a/apps/web/src/pages/api/trpc/[trpc].ts b/apps/web/src/pages/api/trpc/[trpc].ts index 3d6dabc5..5b900cb3 100644 --- a/apps/web/src/pages/api/trpc/[trpc].ts +++ b/apps/web/src/pages/api/trpc/[trpc].ts @@ -1,26 +1,30 @@ -import { type NextRequest } from "next/server"; -import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; +import { type NextApiRequest, type NextApiResponse } from "next"; +import { createNextApiHandler } from "@trpc/server/adapters/next"; import { appRouter } from "@kan/api/root"; import { createTRPCContext } from "@kan/api/trpc"; -export default async function handler(req: NextRequest) { - return fetchRequestHandler({ - endpoint: "/api/trpc", - router: appRouter, - req, - createContext: createTRPCContext, - onError: - process.env.NODE_ENV === "development" - ? ({ path, error }) => { - console.error( - `❌ tRPC failed on ${path ?? ""}: ${error.message}`, - ); - } - : undefined, - }); -} +const nextApiHandler = createNextApiHandler({ + router: appRouter, + createContext: createTRPCContext, + onError: + process.env.NODE_ENV === "development" + ? ({ path, error }) => { + console.error( + `❌ tRPC failed on ${path ?? ""}: ${error.message}`, + ); + } + : undefined, +}); -export const runtime = "edge"; -export const preferredRegion = "lhr1"; -export const dynamic = "force-dynamic"; +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === "OPTIONS") { + res.writeHead(200); + return res.end(); + } + + return nextApiHandler(req, res); +} diff --git a/apps/web/src/pages/api/upload/image.ts b/apps/web/src/pages/api/upload/image.ts index 38ec5e42..6e7162ce 100644 --- a/apps/web/src/pages/api/upload/image.ts +++ b/apps/web/src/pages/api/upload/image.ts @@ -1,19 +1,19 @@ -import type { NextRequest } from "next/server"; +import type { NextApiRequest, NextApiResponse } from "next"; import { S3Client } from "@aws-sdk/client-s3"; import { createPresignedPost } from "@aws-sdk/s3-presigned-post"; import { env } from "~/env"; -export default async function POST(req: NextRequest) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { if (req.method !== "POST") { - return new Response(JSON.stringify({ error: "Method not allowed" }), { - status: 405, - headers: { "Content-Type": "application/json" }, - }); + return res.status(405).json({ error: "Method not allowed" }); } try { - const { filename, contentType } = await req.json(); + const { filename, contentType } = req.body; const client = new S3Client({ forcePathStyle: true, @@ -41,12 +41,8 @@ export default async function POST(req: NextRequest) { const { url, fields } = signedUrl; - return Response.json({ url, fields }); + return res.status(200).json({ url, fields }); } catch (error) { - return Response.json({ error: (error as Error).message }); + return res.status(500).json({ error: (error as Error).message }); } } - -export const runtime = "edge"; -export const preferredRegion = "lhr1"; -export const dynamic = "force-dynamic"; diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 22b45a96..7f84807c 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -1,6 +1,5 @@ -import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; import type { CreateNextContextOptions } from "@trpc/server/adapters/next"; -import type { NextRequest } from "next/server"; +import type { NextApiRequest } from "next"; import type { OpenApiMeta } from "trpc-to-openapi"; import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; @@ -33,25 +32,25 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => { }; }; -export const createTRPCContext = async ({ - req, -}: FetchCreateContextFnOptions) => { +export const createTRPCContext = async ({ req }: CreateNextContextOptions) => { const db = createDrizzleClient(); const auth = initAuth(db); const session = await auth.api.getSession({ - headers: req.headers, + // @ts-expect-error + headers: new Headers(req.headers), }); return createInnerTRPCContext({ db, user: session?.user }); }; -export const createNextApiContext = async (req: NextRequest) => { +export const createNextApiContext = async (req: NextApiRequest) => { const db = createDrizzleClient(); const auth = initAuth(db); const session = await auth.api.getSession({ - headers: req.headers, + // @ts-expect-error + headers: new Headers(req.headers), }); return createInnerTRPCContext({ db, user: session?.user }); diff --git a/packages/db/package.json b/packages/db/package.json index dfe2ea16..cff2a5a4 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -39,16 +39,16 @@ }, "dependencies": { "@kan/shared": "workspace:^", - "@neondatabase/serverless": "^0.10.4", - "@vercel/postgres": "^0.10.0", "drizzle-orm": "^0.42.0", "drizzle-zod": "^0.5.1", + "pg": "^8.11.3", "zod": "catalog:" }, "devDependencies": { "@kan/eslint-config": "workspace:*", "@kan/prettier-config": "workspace:*", "@kan/tsconfig": "workspace:*", + "@types/pg": "^8.10.9", "dotenv-cli": "^7.4.4", "drizzle-kit": "^0.28.1", "eslint": "catalog:", diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts index a83d17ad..eae8f1d2 100644 --- a/packages/db/src/client.ts +++ b/packages/db/src/client.ts @@ -1,20 +1,23 @@ -import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless"; -import { Pool } from "@neondatabase/serverless"; -import { drizzle } from "drizzle-orm/neon-serverless"; +import type { NodePgDatabase } from "drizzle-orm/node-postgres"; +import { drizzle as drizzlePg } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; import * as schema from "./schema"; -export type dbClient = DrizzleClient & { +export type dbClient = NodePgDatabase & { $client: Pool; }; -export const createDrizzleClient = () => { +export const createDrizzleClient = (): dbClient => { + const connectionString = process.env.POSTGRES_URL; + + if (!connectionString) { + throw new Error("POSTGRES_URL environment variable is not set"); + } + const pool = new Pool({ - connectionString: process.env.POSTGRES_URL, + connectionString, }); - return drizzle({ - client: pool, - schema, - }); + return drizzlePg(pool, { schema }) as dbClient; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43456ba9..50208c56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -309,18 +309,15 @@ importers: '@kan/shared': specifier: workspace:^ version: link:../shared - '@neondatabase/serverless': - specifier: ^0.10.4 - version: 0.10.4 - '@vercel/postgres': - specifier: ^0.10.0 - version: 0.10.0(utf-8-validate@6.0.3) drizzle-orm: specifier: ^0.42.0 version: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5) drizzle-zod: specifier: ^0.5.1 version: 0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0) + pg: + specifier: ^8.11.3 + version: 8.13.1 zod: specifier: 'catalog:' version: 3.24.0 @@ -334,6 +331,9 @@ importers: '@kan/tsconfig': specifier: workspace:* version: link:../../tooling/typescript + '@types/pg': + specifier: ^8.10.9 + version: 8.11.6 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -6957,10 +6957,12 @@ snapshots: '@neondatabase/serverless@0.10.4': dependencies: '@types/pg': 8.11.6 + optional: true '@neondatabase/serverless@0.9.5': dependencies: '@types/pg': 8.11.6 + optional: true '@next/env@14.2.20': {} @@ -7973,6 +7975,7 @@ snapshots: ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - utf-8-validate + optional: true abbrev@2.0.0: {} @@ -8256,6 +8259,7 @@ snapshots: bufferutil@4.0.8: dependencies: node-gyp-build: 4.8.4 + optional: true busboy@1.6.0: dependencies: @@ -10465,7 +10469,8 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-gyp-build@4.8.4: {} + node-gyp-build@4.8.4: + optional: true node-plop@0.26.3: dependencies: @@ -10689,8 +10694,7 @@ snapshots: pg-cloudflare@1.1.1: optional: true - pg-connection-string@2.7.0: - optional: true + pg-connection-string@2.7.0: {} pg-int8@1.0.1: {} @@ -10699,7 +10703,6 @@ snapshots: pg-pool@3.7.0(pg@8.13.1): dependencies: pg: 8.13.1 - optional: true pg-protocol@1.7.0: {} @@ -10710,7 +10713,6 @@ snapshots: postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 - optional: true pg-types@4.0.2: dependencies: @@ -10731,12 +10733,10 @@ snapshots: pgpass: 1.0.5 optionalDependencies: pg-cloudflare: 1.1.1 - optional: true pgpass@1.0.5: dependencies: split2: 4.2.0 - optional: true picocolors@1.0.1: {} @@ -10794,27 +10794,23 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgres-array@2.0.0: - optional: true + postgres-array@2.0.0: {} postgres-array@3.0.2: {} - postgres-bytea@1.0.0: - optional: true + postgres-bytea@1.0.0: {} postgres-bytea@3.0.0: dependencies: obuf: 1.1.2 - postgres-date@1.0.7: - optional: true + postgres-date@1.0.7: {} postgres-date@2.1.0: {} postgres-interval@1.2.0: dependencies: xtend: 4.0.2 - optional: true postgres-interval@3.0.0: {} @@ -11331,8 +11327,7 @@ snapshots: source-map@0.6.1: {} - split2@4.2.0: - optional: true + split2@4.2.0: {} sprintf-js@1.0.3: {} @@ -11913,6 +11908,7 @@ snapshots: optionalDependencies: bufferutil: 4.0.8 utf-8-validate: 6.0.3 + optional: true xml2js@0.6.2: dependencies: @@ -11921,8 +11917,7 @@ snapshots: xmlbuilder@11.0.1: {} - xtend@4.0.2: - optional: true + xtend@4.0.2: {} y18n@5.0.8: {}