Skip to content

Commit

Permalink
feat: trpc setup and auth connected to db
Browse files Browse the repository at this point in the history
  • Loading branch information
mrevanzak committed May 21, 2024
1 parent 7c36354 commit 17c6a84
Show file tree
Hide file tree
Showing 27 changed files with 465 additions and 7,942 deletions.
10 changes: 10 additions & 0 deletions apps/web/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Config } from "drizzle-kit";
import { env } from "@/env";

export default {
schema: "./src/server/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: env.POSTGRES_URL,
},
} satisfies Config;
14 changes: 13 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"scripts": {
"build": "bun with-env next build",
"clean": "git clean -xdf .next .turbo node_modules",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"dev": "bun with-env next dev",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
Expand All @@ -14,11 +16,18 @@
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@auth/drizzle-adapter": "^1.1.0",
"@t3-oss/env-nextjs": "^0.10.0",
"@tanstack/react-query": "^5.36.0",
"@tanstack/react-query": "^5.25.0",
"@tanya.in/ui": "*",
"@trpc/client": "next",
"@trpc/react-query": "next",
"@trpc/server": "next",
"@vercel/analytics": "^1.2.2",
"@vercel/edge-config": "^1.1.0",
"@vercel/postgres": "^0.8.0",
"drizzle-orm": "^0.30.10",
"drizzle-zod": "^0.5.1",
"geist": "^1.3.0",
"next": "^14.2.0",
"next-auth": "beta",
Expand All @@ -33,10 +42,13 @@
"@tanya.in/eslint-config": "*",
"@tanya.in/prettier-config": "*",
"@tanya.in/tsconfig": "*",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^20.12.5",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"bcryptjs": "^2.4.3",
"dotenv-cli": "^7.4.1",
"drizzle-kit": "^0.21.2",
"eslint": "^9.2.0",
"jiti": "^1.21.0",
"prettier": "^3.2.5",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Footer } from "@/components/footer";
import { Navbar } from "@/components/navbar";
import { SidebarWrapper } from "@/components/sidebar/sidebar";
import { auth } from "@/lib/auth";
import { auth } from "@/server/auth";
import { get } from "@vercel/edge-config";

export default async function AuthLayout(props: {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { GET, POST } from "@/lib/auth";
export { GET, POST } from "@/server/auth";

export const runtime = "edge";
33 changes: 33 additions & 0 deletions apps/web/src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { NextRequest } from "next/server";
import { env } from "@/env";
import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";

/**
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
* handling a HTTP request (e.g. when you make requests from Client Components).
*/
const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext(req),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
});

export { handler as GET, handler as POST };
2 changes: 1 addition & 1 deletion apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "@/styles/globals.css";

import { Providers } from "@/components/providers";
import { siteConfig } from "@/constant/config";
import { auth } from "@/lib/auth";
import { auth } from "@/server/auth";
import { SessionProvider } from "next-auth/react";

export const metadata: Metadata = {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { auth } from "@/lib/auth";
import { auth } from "@/server/auth";

import { cn } from "@tanya.in/ui";

Expand Down
43 changes: 3 additions & 40 deletions apps/web/src/components/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@

import * as React from "react";
import { useLockedBody } from "@/lib/hooks/useBodyLock";
import { TRPCReactProvider } from "@/trpc/react";
import { NextUIProvider } from "@nextui-org/system";
import {
MutationCache,
QueryCache,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { toast } from "sonner";
import { ZodError } from "zod";

import { ThemeProvider } from "@tanya.in/ui/theme";

Expand All @@ -24,40 +17,10 @@ export function Providers({ children }: { children: React.ReactNode }) {
setLocked(!sidebarOpen);
};

const [queryClient] = React.useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
queryCache: new QueryCache({
onError: (err) => {
if (err instanceof ZodError) {
err.issues.map((issue) =>
toast.error(`${issue.path.toString()}: ${issue.message}`),
);
return;
}

toast.error(err.message);
},
}),
mutationCache: new MutationCache({
onError: (err) => {
toast.error(err.message);
},
}),
}),
);

return (
<NextUIProvider>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<QueryClientProvider client={queryClient}>
<TRPCReactProvider>
<SidebarContext.Provider
value={{
collapsed: sidebarOpen,
Expand All @@ -66,7 +29,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
>
{children}
</SidebarContext.Provider>
</QueryClientProvider>
</TRPCReactProvider>
</ThemeProvider>
</NextUIProvider>
);
Expand Down
12 changes: 8 additions & 4 deletions apps/web/src/components/signin-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from "react";
import Link from "next/link";
import useSignIn from "@/lib/api/auth/sign-in";
import { signIn } from "@/lib/actions/auth";
import { signInFormSchema } from "@/lib/validation/sign-in";
import { FaEye, FaEyeSlash } from "react-icons/fa";

Expand All @@ -15,11 +15,12 @@ import {
CardTitle,
} from "@tanya.in/ui/card";
import { Form, FormInput, useForm } from "@tanya.in/ui/form";
import { toast } from "@tanya.in/ui/toast";

export function SignInForm() {
const [isVisible, setIsVisible] = React.useState(false);
const [isPending, setIsPending] = React.useState(false);

const { mutate, isPending } = useSignIn();
const methods = useForm({
schema: signInFormSchema,
mode: "onTouched",
Expand All @@ -38,8 +39,11 @@ export function SignInForm() {
<Form {...methods}>
<form
className="space-y-4"
onSubmit={handleSubmit((data) => {
mutate(data);
onSubmit={handleSubmit(async (data) => {
setIsPending(true);
const res = await signIn(data.email, data.password);
if (res?.error) toast.error(res.error);
setIsPending(false);
})}
>
<FormInput name="email" type="email" control={control} />
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const env = createEnv({
server: {
AUTH_SECRET: z.string().min(1),
EDGE_CONFIG: z.string().optional(),
POSTGRES_URL: z.string().min(1),
PORT: z.string().default("3000"),
},

/**
Expand Down
21 changes: 21 additions & 0 deletions apps/web/src/lib/actions/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use server";

import { signIn as attempt } from "@/server/auth";
import { AuthError } from "next-auth";

export async function signIn(email: string, password: string) {
try {
await attempt("credentials", {
email: email,
password: password,
redirectTo: "/",
});
} catch (error) {
if (error instanceof AuthError) {
return {
error: error.cause?.err?.message ?? "Oops! Something went wrong!",
};
}
throw error;
}
}
32 changes: 0 additions & 32 deletions apps/web/src/lib/api/auth/sign-in.ts

This file was deleted.

5 changes: 4 additions & 1 deletion apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { auth } from "@/lib/auth";
import authConfig from "@/server/auth.config";
import NextAuth from "next-auth";

const publicRoutes = [""];
const authRoutes = ["/sign-in"];

const { auth } = NextAuth(authConfig);

export default auth((req) => {
if (req.auth?.user && authRoutes.includes(req.nextUrl.pathname)) {
return Response.redirect(new URL("/", req.url));
Expand Down
24 changes: 24 additions & 0 deletions apps/web/src/server/api/root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { authRouter } from "@/server/api/routers/auth/auth.procedure";

import { createCallerFactory, createTRPCRouter } from "./trpc";

/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
auth: authRouter,
});

// export type definition of API
export type AppRouter = typeof appRouter;

/**
* Create a server-side caller for the tRPC API.
* @example
* const trpc = createCaller(createContext);
* const res = await trpc.post.all();
* ^? Post[]
*/
export const createCaller = createCallerFactory(appRouter);
7 changes: 7 additions & 0 deletions apps/web/src/server/api/routers/auth/auth.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { users } from "@/server/db/schema";
import { createInsertSchema } from "drizzle-zod";

export const authSchema = createInsertSchema(users).pick({
email: true,
password: true,
});
24 changes: 24 additions & 0 deletions apps/web/src/server/api/routers/auth/auth.procedure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";
import { signIn } from "@/server/auth";
import { TRPCError } from "@trpc/server";
import { AuthError } from "next-auth";

import { authSchema } from "./auth.input";

export const authRouter = createTRPCRouter({
login: publicProcedure.input(authSchema).mutation(async ({ input }) => {
try {
await signIn("credentials", {
email: input.email,
password: input.password,
redirectTo: "/",
});
} catch (error) {
if (error instanceof AuthError)
throw new TRPCError({
code: "BAD_REQUEST",
message: error.cause?.err?.message ?? "Oops! Something went wrong!",
});
}
}),
});
Loading

0 comments on commit 17c6a84

Please sign in to comment.