refactor: remove edge compatibility

This commit is contained in:
Henry
2025-05-15 22:27:03 +01:00
parent 66de4b6c42
commit d065ae73e1
11 changed files with 108 additions and 167 deletions

View File

@@ -8,7 +8,3 @@ export const config = { api: { bodyParser: false } };
const auth = initAuth(createDrizzleClient()); const auth = initAuth(createDrizzleClient());
export default toNodeHandler(auth.handler); export default toNodeHandler(auth.handler);
// export const runtime = "edge";
// export const preferredRegion = "lhr1";
// export const dynamic = "force-dynamic";

View File

@@ -1,5 +1,5 @@
// import type { EmailOtpType } from "@supabase/supabase-js"; // import type { EmailOtpType } from "@supabase/supabase-js";
// import type { NextRequest } from "next/server"; // import type { NextApiRequest } from "next";
// import { NextResponse } from "next/server"; // import { NextResponse } from "next/server";
// import { createDrizzleClient } from "@kan/db/client"; // import { createDrizzleClient } from "@kan/db/client";
@@ -8,7 +8,7 @@
// import { stripe } from "@kan/stripe"; // import { stripe } from "@kan/stripe";
// import { createNextApiClient } from "@kan/"; // import { createNextApiClient } from "@kan/";
// export default async function handler(req: NextRequest) { // export default async function handler(req: NextApiRequest) {
// if (req.method !== "GET") { // if (req.method !== "GET") {
// return new NextResponse(null, { // return new NextResponse(null, {
// status: 405, // status: 405,
@@ -96,7 +96,3 @@
// return redirectResponse; // return redirectResponse;
// } // }
// export const runtime = "edge";
// export const preferredRegion = "lhr1";
// export const dynamic = "force-dynamic";

View File

@@ -1,29 +1,23 @@
import type { NextRequest } from "next/server"; import type { NextApiRequest, NextApiResponse } from "next";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
import { createStripeClient } from "@kan/stripe"; import { createStripeClient } from "@kan/stripe";
export default async function handler(req: NextRequest) { export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const stripe = createStripeClient(); const stripe = createStripeClient();
if (req.method !== "POST") { if (req.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed" }), { return res.status(405).json({ error: "Method not allowed" });
status: 405,
headers: { "Content-Type": "application/json" },
});
} }
try { try {
const { user } = await createNextApiContext(req); const { user } = await createNextApiContext(req);
if (!user?.stripeCustomerId) { if (!user?.stripeCustomerId) {
return new Response( return res.status(404).json({ error: "No billing account found" });
JSON.stringify({ error: "No billing account found" }),
{
status: 404,
headers: { "Content-Type": "application/json" },
},
);
} }
const session = await stripe.billingPortal.sessions.create({ 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_url: `${process.env.WEBSITE_URL}/settings`,
}); });
return new Response(JSON.stringify({ url: session.url }), { return res.status(200).json({ url: session.url });
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);
return new Response( return res.status(500).json({ error: "Error creating portal session" });
JSON.stringify({ error: "Error creating portal session" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
},
);
} }
} }
export const runtime = "edge";
export const preferredRegion = "lhr1";
export const dynamic = "force-dynamic";

View File

@@ -1,4 +1,4 @@
import type { NextRequest } from "next/server"; import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod"; import { z } from "zod";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
@@ -19,37 +19,28 @@ interface CheckoutSessionRequest {
stripeCustomerId: string; stripeCustomerId: string;
} }
export default async function handler(req: NextRequest) { export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const stripe = createStripeClient(); const stripe = createStripeClient();
if (req.method !== "POST") { if (req.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed" }), { return res.status(405).json({ error: "Method not allowed" });
status: 405,
headers: { "Content-Type": "application/json" },
});
} }
try { try {
const { user, db } = await createNextApiContext(req); const { user, db } = await createNextApiContext(req);
if (!user) { if (!user) {
return new Response(JSON.stringify({ error: "User not found" }), { return res.status(404).json({ error: "User not found" });
status: 404,
headers: { "Content-Type": "application/json" },
});
} }
const body = (await req.json()) as CheckoutSessionRequest; const body = req.body as CheckoutSessionRequest;
const { successUrl, cancelUrl, slug, workspacePublicId } = body; const { successUrl, cancelUrl, slug, workspacePublicId } = body;
if (!successUrl || !cancelUrl || !slug || !workspacePublicId) { if (!successUrl || !cancelUrl || !slug || !workspacePublicId) {
return new Response( return res.status(400).json({ error: "Missing required fields" });
JSON.stringify({ error: "Missing required fields" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
},
);
} }
const slugResult = workspaceSlugSchema.safeParse(slug); const slugResult = workspaceSlugSchema.safeParse(slug);
@@ -91,22 +82,9 @@ export default async function handler(req: NextRequest) {
}, },
}); });
return new Response(JSON.stringify({ url: session.url }), { return res.status(200).json({ url: session.url });
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);
return new Response( return res.status(500).json({ error: "Error creating checkout session" });
JSON.stringify({ error: "Error creating checkout session" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
},
);
} }
} }
export const runtime = "edge";
export const preferredRegion = "lhr1";
export const dynamic = "force-dynamic";

View File

@@ -1,4 +1,4 @@
import type { NextRequest } from "next/server"; import type { NextApiRequest, NextApiResponse } from "next";
import { Stripe } from "stripe"; import { Stripe } from "stripe";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
@@ -7,25 +7,24 @@ import { createStripeClient } from "@kan/stripe";
export const webCrypto = Stripe.createSubtleCryptoProvider(); export const webCrypto = Stripe.createSubtleCryptoProvider();
export default async function handler(req: NextRequest) { export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const stripe = createStripeClient(); const stripe = createStripeClient();
if (req.method !== "POST") { if (req.method !== "POST") {
return new Response(JSON.stringify({ message: "Method not allowed" }), { return res.status(405).json({ message: "Method not allowed" });
status: 405,
});
} }
const sig = req.headers.get("stripe-signature"); const sig = req.headers["stripe-signature"];
if (!sig) { if (!sig) {
return new Response(JSON.stringify({ message: "No signature found" }), { return res.status(400).json({ message: "No signature found" });
status: 400,
});
} }
try { try {
const body = await req.text(); const body = req.body;
const event = await stripe.webhooks.constructEventAsync( const event = await stripe.webhooks.constructEventAsync(
body, body,
@@ -55,15 +54,9 @@ export default async function handler(req: NextRequest) {
console.log(`Unhandled event type: ${event.type}`); 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) { } catch (err) {
console.error("Webhook error:", err); console.error("Webhook error:", err);
return new Response(JSON.stringify({ message: "Webhook handler failed" }), { return res.status(400).json({ message: "Webhook handler failed" });
status: 400,
});
} }
} }
export const runtime = "edge";
export const preferredRegion = "lhr1";
export const dynamic = "force-dynamic";

View File

@@ -1,26 +1,30 @@
import { type NextRequest } from "next/server"; import { type NextApiRequest, type NextApiResponse } from "next";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { createNextApiHandler } from "@trpc/server/adapters/next";
import { appRouter } from "@kan/api/root"; import { appRouter } from "@kan/api/root";
import { createTRPCContext } from "@kan/api/trpc"; import { createTRPCContext } from "@kan/api/trpc";
export default async function handler(req: NextRequest) { const nextApiHandler = createNextApiHandler({
return fetchRequestHandler({ router: appRouter,
endpoint: "/api/trpc", createContext: createTRPCContext,
router: appRouter, onError:
req, process.env.NODE_ENV === "development"
createContext: createTRPCContext, ? ({ path, error }) => {
onError: console.error(
process.env.NODE_ENV === "development" `❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
? ({ path, error }) => { );
console.error( }
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`, : undefined,
); });
}
: undefined,
});
}
export const runtime = "edge"; export default async function handler(
export const preferredRegion = "lhr1"; req: NextApiRequest,
export const dynamic = "force-dynamic"; res: NextApiResponse,
) {
if (req.method === "OPTIONS") {
res.writeHead(200);
return res.end();
}
return nextApiHandler(req, res);
}

View File

@@ -1,19 +1,19 @@
import type { NextRequest } from "next/server"; import type { NextApiRequest, NextApiResponse } from "next";
import { S3Client } from "@aws-sdk/client-s3"; import { S3Client } from "@aws-sdk/client-s3";
import { createPresignedPost } from "@aws-sdk/s3-presigned-post"; import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
import { env } from "~/env"; import { env } from "~/env";
export default async function POST(req: NextRequest) { export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") { if (req.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed" }), { return res.status(405).json({ error: "Method not allowed" });
status: 405,
headers: { "Content-Type": "application/json" },
});
} }
try { try {
const { filename, contentType } = await req.json(); const { filename, contentType } = req.body;
const client = new S3Client({ const client = new S3Client({
forcePathStyle: true, forcePathStyle: true,
@@ -41,12 +41,8 @@ export default async function POST(req: NextRequest) {
const { url, fields } = signedUrl; const { url, fields } = signedUrl;
return Response.json({ url, fields }); return res.status(200).json({ url, fields });
} catch (error) { } 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";

View File

@@ -1,6 +1,5 @@
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next"; 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 type { OpenApiMeta } from "trpc-to-openapi";
import { initTRPC, TRPCError } from "@trpc/server"; import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson"; import superjson from "superjson";
@@ -33,25 +32,25 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
}; };
}; };
export const createTRPCContext = async ({ export const createTRPCContext = async ({ req }: CreateNextContextOptions) => {
req,
}: FetchCreateContextFnOptions) => {
const db = createDrizzleClient(); const db = createDrizzleClient();
const auth = initAuth(db); const auth = initAuth(db);
const session = await auth.api.getSession({ const session = await auth.api.getSession({
headers: req.headers, // @ts-expect-error
headers: new Headers(req.headers),
}); });
return createInnerTRPCContext({ db, user: session?.user }); return createInnerTRPCContext({ db, user: session?.user });
}; };
export const createNextApiContext = async (req: NextRequest) => { export const createNextApiContext = async (req: NextApiRequest) => {
const db = createDrizzleClient(); const db = createDrizzleClient();
const auth = initAuth(db); const auth = initAuth(db);
const session = await auth.api.getSession({ const session = await auth.api.getSession({
headers: req.headers, // @ts-expect-error
headers: new Headers(req.headers),
}); });
return createInnerTRPCContext({ db, user: session?.user }); return createInnerTRPCContext({ db, user: session?.user });

View File

@@ -39,16 +39,16 @@
}, },
"dependencies": { "dependencies": {
"@kan/shared": "workspace:^", "@kan/shared": "workspace:^",
"@neondatabase/serverless": "^0.10.4",
"@vercel/postgres": "^0.10.0",
"drizzle-orm": "^0.42.0", "drizzle-orm": "^0.42.0",
"drizzle-zod": "^0.5.1", "drizzle-zod": "^0.5.1",
"pg": "^8.11.3",
"zod": "catalog:" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@kan/eslint-config": "workspace:*", "@kan/eslint-config": "workspace:*",
"@kan/prettier-config": "workspace:*", "@kan/prettier-config": "workspace:*",
"@kan/tsconfig": "workspace:*", "@kan/tsconfig": "workspace:*",
"@types/pg": "^8.10.9",
"dotenv-cli": "^7.4.4", "dotenv-cli": "^7.4.4",
"drizzle-kit": "^0.28.1", "drizzle-kit": "^0.28.1",
"eslint": "catalog:", "eslint": "catalog:",

View File

@@ -1,20 +1,23 @@
import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless"; import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { Pool } from "@neondatabase/serverless"; import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
import { drizzle } from "drizzle-orm/neon-serverless"; import { Pool } from "pg";
import * as schema from "./schema"; import * as schema from "./schema";
export type dbClient = DrizzleClient<typeof schema> & { export type dbClient = NodePgDatabase<typeof schema> & {
$client: Pool; $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({ const pool = new Pool({
connectionString: process.env.POSTGRES_URL, connectionString,
}); });
return drizzle({ return drizzlePg(pool, { schema }) as dbClient;
client: pool,
schema,
});
}; };

43
pnpm-lock.yaml generated
View File

@@ -309,18 +309,15 @@ importers:
'@kan/shared': '@kan/shared':
specifier: workspace:^ specifier: workspace:^
version: link:../shared 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: drizzle-orm:
specifier: ^0.42.0 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) 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: drizzle-zod:
specifier: ^0.5.1 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) 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: zod:
specifier: 'catalog:' specifier: 'catalog:'
version: 3.24.0 version: 3.24.0
@@ -334,6 +331,9 @@ importers:
'@kan/tsconfig': '@kan/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/typescript version: link:../../tooling/typescript
'@types/pg':
specifier: ^8.10.9
version: 8.11.6
dotenv-cli: dotenv-cli:
specifier: ^7.4.4 specifier: ^7.4.4
version: 7.4.4 version: 7.4.4
@@ -6957,10 +6957,12 @@ snapshots:
'@neondatabase/serverless@0.10.4': '@neondatabase/serverless@0.10.4':
dependencies: dependencies:
'@types/pg': 8.11.6 '@types/pg': 8.11.6
optional: true
'@neondatabase/serverless@0.9.5': '@neondatabase/serverless@0.9.5':
dependencies: dependencies:
'@types/pg': 8.11.6 '@types/pg': 8.11.6
optional: true
'@next/env@14.2.20': {} '@next/env@14.2.20': {}
@@ -7973,6 +7975,7 @@ snapshots:
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.3)
transitivePeerDependencies: transitivePeerDependencies:
- utf-8-validate - utf-8-validate
optional: true
abbrev@2.0.0: {} abbrev@2.0.0: {}
@@ -8256,6 +8259,7 @@ snapshots:
bufferutil@4.0.8: bufferutil@4.0.8:
dependencies: dependencies:
node-gyp-build: 4.8.4 node-gyp-build: 4.8.4
optional: true
busboy@1.6.0: busboy@1.6.0:
dependencies: dependencies:
@@ -10465,7 +10469,8 @@ snapshots:
dependencies: dependencies:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
node-gyp-build@4.8.4: {} node-gyp-build@4.8.4:
optional: true
node-plop@0.26.3: node-plop@0.26.3:
dependencies: dependencies:
@@ -10689,8 +10694,7 @@ snapshots:
pg-cloudflare@1.1.1: pg-cloudflare@1.1.1:
optional: true optional: true
pg-connection-string@2.7.0: pg-connection-string@2.7.0: {}
optional: true
pg-int8@1.0.1: {} pg-int8@1.0.1: {}
@@ -10699,7 +10703,6 @@ snapshots:
pg-pool@3.7.0(pg@8.13.1): pg-pool@3.7.0(pg@8.13.1):
dependencies: dependencies:
pg: 8.13.1 pg: 8.13.1
optional: true
pg-protocol@1.7.0: {} pg-protocol@1.7.0: {}
@@ -10710,7 +10713,6 @@ snapshots:
postgres-bytea: 1.0.0 postgres-bytea: 1.0.0
postgres-date: 1.0.7 postgres-date: 1.0.7
postgres-interval: 1.2.0 postgres-interval: 1.2.0
optional: true
pg-types@4.0.2: pg-types@4.0.2:
dependencies: dependencies:
@@ -10731,12 +10733,10 @@ snapshots:
pgpass: 1.0.5 pgpass: 1.0.5
optionalDependencies: optionalDependencies:
pg-cloudflare: 1.1.1 pg-cloudflare: 1.1.1
optional: true
pgpass@1.0.5: pgpass@1.0.5:
dependencies: dependencies:
split2: 4.2.0 split2: 4.2.0
optional: true
picocolors@1.0.1: {} picocolors@1.0.1: {}
@@ -10794,27 +10794,23 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
postgres-array@2.0.0: postgres-array@2.0.0: {}
optional: true
postgres-array@3.0.2: {} postgres-array@3.0.2: {}
postgres-bytea@1.0.0: postgres-bytea@1.0.0: {}
optional: true
postgres-bytea@3.0.0: postgres-bytea@3.0.0:
dependencies: dependencies:
obuf: 1.1.2 obuf: 1.1.2
postgres-date@1.0.7: postgres-date@1.0.7: {}
optional: true
postgres-date@2.1.0: {} postgres-date@2.1.0: {}
postgres-interval@1.2.0: postgres-interval@1.2.0:
dependencies: dependencies:
xtend: 4.0.2 xtend: 4.0.2
optional: true
postgres-interval@3.0.0: {} postgres-interval@3.0.0: {}
@@ -11331,8 +11327,7 @@ snapshots:
source-map@0.6.1: {} source-map@0.6.1: {}
split2@4.2.0: split2@4.2.0: {}
optional: true
sprintf-js@1.0.3: {} sprintf-js@1.0.3: {}
@@ -11913,6 +11908,7 @@ snapshots:
optionalDependencies: optionalDependencies:
bufferutil: 4.0.8 bufferutil: 4.0.8
utf-8-validate: 6.0.3 utf-8-validate: 6.0.3
optional: true
xml2js@0.6.2: xml2js@0.6.2:
dependencies: dependencies:
@@ -11921,8 +11917,7 @@ snapshots:
xmlbuilder@11.0.1: {} xmlbuilder@11.0.1: {}
xtend@4.0.2: xtend@4.0.2: {}
optional: true
y18n@5.0.8: {} y18n@5.0.8: {}