Skip to content

Commit

Permalink
feat: replace next-auth with clerk
Browse files Browse the repository at this point in the history
  • Loading branch information
mrevanzak committed Oct 30, 2023
1 parent bb0fd2e commit e7852ab
Show file tree
Hide file tree
Showing 24 changed files with 319 additions and 519 deletions.
3 changes: 1 addition & 2 deletions apps/nextjs/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// Importing env files here to validate on build
import "./src/env.mjs";
import "@vivat/auth/env.mjs";

/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: true,
/** Enables hot reloading for local packages without a build step */
transpilePackages: ["@vivat/api", "@vivat/auth", "@vivat/db"],
transpilePackages: ["@vivat/api", "@vivat/db"],
/** We already do linting and typechecking as separate tasks in CI */
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
Expand Down
1 change: 0 additions & 1 deletion apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"@trpc/react-query": "^10.40.0",
"@trpc/server": "^10.40.0",
"@vivat/api": "^0.1.0",
"@vivat/auth": "^0.1.0",
"@vivat/db": "^0.1.0",
"next": "^13.5.4",
"react": "18.2.0",
Expand Down
50 changes: 25 additions & 25 deletions apps/nextjs/src/app/_components/auth-showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { auth } from "@vivat/auth";
// import { auth } from "@vivat/auth";

import { SignIn, SignOut } from "~/components/auth";
// import { SignIn, SignOut } from "~/components/auth";

export async function AuthShowcase() {
const session = await auth();
// export async function AuthShowcase() {
// const session = await auth();

if (!session) {
return (
<SignIn
provider="discord"
className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
>
Sign in with Discord
</SignIn>
);
}
// if (!session) {
// return (
// <SignIn
// provider="discord"
// className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
// >
// Sign in with Discord
// </SignIn>
// );
// }

return (
<div className="flex flex-col items-center justify-center gap-4">
<p className="text-center text-2xl text-white">
{session && <span>Logged in as {session.user.name}</span>}
</p>
// return (
// <div className="flex flex-col items-center justify-center gap-4">
// <p className="text-center text-2xl text-white">
// {session && <span>Logged in as {session.user.name}</span>}
// </p>

<SignOut className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20">
Sign out
</SignOut>
</div>
);
}
// <SignOut className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20">
// Sign out
// </SignOut>
// </div>
// );
// }
3 changes: 0 additions & 3 deletions apps/nextjs/src/app/api/auth/[...nextauth]/route.ts

This file was deleted.

100 changes: 100 additions & 0 deletions apps/nextjs/src/app/api/webhook/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { headers } from "next/headers";
import type { User } from "@clerk/nextjs/api";
import { Webhook } from "svix";

import { db, eq } from "@vivat/db";
import { users } from "@vivat/db/schema/user";

type UnwantedKeys =
| "emailAddresses"
| "firstName"
| "lastName"
| "primaryEmailAddressId"
| "primaryPhoneNumberId"
| "phoneNumbers";

interface UserInterface extends Omit<User, UnwantedKeys> {
email_addresses: {
email_address: string;
id: string;
}[];
primary_email_address_id: string;
first_name: string;
last_name: string;
primary_phone_number_id: string;
phone_numbers: {
phone_number: string;
id: string;
}[];
image_url: string;
}

const webhookSecret: string = process.env.WEBHOOK_SECRET ?? "";

export async function POST(req: Request) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = await req.json();
const payloadString = JSON.stringify(payload);
const headerPayload = headers();
const svixId = headerPayload.get("svix-id");
const svixIdTimeStamp = headerPayload.get("svix-timestamp");
const svixSignature = headerPayload.get("svix-signature");
if (!svixId || !svixIdTimeStamp || !svixSignature) {
console.log("svixId", svixId);
console.log("svixIdTimeStamp", svixIdTimeStamp);
console.log("svixSignature", svixSignature);
return new Response("Error occured", {
status: 400,
});
}
const svixHeaders = {
"svix-id": svixId,
"svix-timestamp": svixIdTimeStamp,
"svix-signature": svixSignature,
};
const wh = new Webhook(webhookSecret);
let evt: Event | null = null;
try {
evt = wh.verify(payloadString, svixHeaders) as Event;
} catch (_) {
console.log("error");
return new Response("Error occured", {
status: 400,
});
}
const { id } = evt.data;
// Handle the webhook
const eventType: EventType = evt.type;
if (eventType === "user.created" || eventType === "user.updated") {
const { email_addresses, primary_email_address_id } = evt.data;
const emailObject = email_addresses?.find((email) => {
return email.id === primary_email_address_id;
});
if (!emailObject) {
return new Response("Error locating user", {
status: 400,
});
}
await db.insert(users).values({
id,
email: emailObject.email_address,
name: evt.data.first_name + " " + evt.data.last_name,
imageUrl: evt.data.image_url,
});
}
if (eventType === "user.deleted") {
await db.delete(users).where(eq(users.id, id));
}
console.log(`User ${id} was ${eventType}`);
return new Response("", {
status: 201,
});
}

interface Event {
data: UserInterface;
object: "event";
type: EventType;
}

type EventType = "user.created" | "user.updated" | "user.deleted";
44 changes: 22 additions & 22 deletions apps/nextjs/src/components/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import type { ComponentProps } from "react";
// import type { ComponentProps } from "react";

import type { OAuthProviders } from "@vivat/auth";
import { CSRF_experimental } from "@vivat/auth";
// import type { OAuthProviders } from "@vivat/auth";
// import { CSRF_experimental } from "@vivat/auth";

export function SignIn({
provider,
...props
}: { provider: OAuthProviders } & ComponentProps<"button">) {
return (
<form action={`/api/auth/signin/${provider}`} method="post">
<button {...props} />
<CSRF_experimental />
</form>
);
}
// export function SignIn({
// provider,
// ...props
// }: { provider: OAuthProviders } & ComponentProps<"button">) {
// return (
// <form action={`/api/auth/signin/${provider}`} method="post">
// <button {...props} />
// <CSRF_experimental />
// </form>
// );
// }

export function SignOut(props: ComponentProps<"button">) {
return (
<form action="/api/auth/signout" method="post">
<button {...props} />
<CSRF_experimental />
</form>
);
}
// export function SignOut(props: ComponentProps<"button">) {
// return (
// <form action="/api/auth/signout" method="post">
// <button {...props} />
// <CSRF_experimental />
// </form>
// );
// }
8 changes: 7 additions & 1 deletion apps/nextjs/src/env.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";





export const env = createEnv({
shared: {
VERCEL_URL: z
Expand All @@ -15,6 +19,7 @@ export const env = createEnv({
*/
server: {
DATABASE_URL: z.string().url(),
WEBHOOK_SECRET: z.string(),
},
/**
* Specify your client-side environment variables schema here.
Expand All @@ -30,10 +35,11 @@ export const env = createEnv({
VERCEL_URL: process.env.VERCEL_URL,
PORT: process.env.PORT,
DATABASE_URL: process.env.DATABASE_URL,
WEBHOOK_SECRET: process.env.WEBHOOK_SECRET,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},
skipValidation:
!!process.env.CI ||
!!process.env.SKIP_ENV_VALIDATION ||
process.env.npm_lifecycle_event === "lint",
});
});
9 changes: 9 additions & 0 deletions apps/nextjs/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { authMiddleware } from "@clerk/nextjs";

export default authMiddleware({
publicRoutes: ["/api/webhook"],
});

export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getAuth } from "@clerk/nextjs/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";

import { appRouter, createTRPCContext } from "@vivat/api";
import { auth } from "@vivat/auth";
import { appRouter, createInnerTRPCContext } from "@vivat/api";
import type { NextRequest } from "next/server";

export const runtime = "edge";

Expand All @@ -24,19 +25,20 @@ export function OPTIONS() {
return response;
}

const handler = auth(async (req) => {
const handler = async (req: NextRequest) => {
const auth = getAuth(req);
const response = await fetchRequestHandler({
endpoint: "/api/trpc",
router: appRouter,
req,
createContext: () => createTRPCContext({ auth: req.auth, req }),
createContext: () => createInnerTRPCContext({ auth }),
onError({ error, path }) {
console.error(`>>> tRPC Error on '${path}'`, error);
},
});

setCorsHeaders(response);
return response;
});
};

export { handler as GET, handler as POST };
44 changes: 44 additions & 0 deletions apps/nextjs/src/pages/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getAuth } from "@clerk/nextjs/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";

import { appRouter, createInnerTRPCContext } from "@vivat/api";
import type { NextRequest } from "next/server";

export const runtime = "edge";

/**
* Configure basic CORS headers
* You should extend this to match your needs
*/
function setCorsHeaders(res: Response) {
res.headers.set("Access-Control-Allow-Origin", "*");
res.headers.set("Access-Control-Request-Method", "*");
res.headers.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST");
res.headers.set("Access-Control-Allow-Headers", "*");
}

export function OPTIONS() {
const response = new Response(null, {
status: 204,
});
setCorsHeaders(response);
return response;
}

const handler = async (req: NextRequest) => {
const auth = getAuth(req);
const response = await fetchRequestHandler({
endpoint: "/api/trpc",
router: appRouter,
req,
createContext: () => createInnerTRPCContext({ auth }),
onError({ error, path }) {
console.error(`>>> tRPC Error on '${path}'`, error);
},
});

setCorsHeaders(response);
return response;
};

export { handler as GET, handler as POST };
16 changes: 12 additions & 4 deletions apps/nextjs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
"~/*": [
"./src/*"
]
},
"plugins": [
{
"name": "next"
}
],
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
"strictNullChecks": true
},
"include": [".", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
".",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
2 changes: 1 addition & 1 deletion packages/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
import type { AppRouter } from "./src/root";

export { appRouter, type AppRouter } from "./src/root";
export { createTRPCContext } from "./src/trpc";
export { createInnerTRPCContext, createTRPCContext } from "./src/trpc";

/**
* Inference helpers for input types
Expand Down
Loading

0 comments on commit e7852ab

Please sign in to comment.