Skip to content

Commit fc445dd

Browse files
authored
feat: Configure next-auth & trpc (#3)
1 parent 4ae99fb commit fc445dd

File tree

10 files changed

+875
-93
lines changed

10 files changed

+875
-93
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,4 @@ dist
130130
.pnp.*
131131

132132
.turbo/
133+
.vercel

apps/webapp/drizzle.config.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import "dotenv/config";
2+
import { defineConfig } from "drizzle-kit";
3+
4+
export default defineConfig({
5+
dialect: "postgresql",
6+
schema: "./src/server/schema.ts",
7+
out: "./drizzle",
8+
dbCredentials: {
9+
url: process.env.DATABASE_URL!,
10+
},
11+
});

apps/webapp/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6+
"db:push": "drizzle-kit push",
7+
"db:studio": "drizzle-kit studio",
68
"dev": "next dev -p 9000",
79
"build": "next build",
810
"start": "next start",
911
"lint": "next lint"
1012
},
1113
"dependencies": {
1214
"@auth/drizzle-adapter": "^1.7.4",
15+
"@tanstack/react-query": "^5.62.10",
1316
"@trpc/client": "11.0.0-rc.666",
14-
"@trpc/next": "11.0.0-rc.666",
1517
"@trpc/react-query": "11.0.0-rc.666",
1618
"@trpc/server": "11.0.0-rc.666",
1719
"class-variance-authority": "^0.7.1",
@@ -32,6 +34,8 @@
3234
"@types/node": "^20",
3335
"@types/react": "^19",
3436
"@types/react-dom": "^19",
37+
"dotenv": "^16.4.7",
38+
"drizzle-kit": "^0.30.1",
3539
"eslint": "^9",
3640
"eslint-config-next": "15.1.1-canary.19",
3741
"postcss": "^8",

apps/webapp/src/app/layout.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import "./globals.css";
44
import { Inter } from "next/font/google";
55
import { SessionProvider } from "next-auth/react";
66
import { NoSSR } from "../components/util/no-ssr";
7-
8-
const inter = Inter({ subsets: ["latin"] });
7+
import { TrpcProvider } from "./trpc-provider";
98

109
// export const metadata = {
1110
// title: "Dudy TPP",
@@ -21,7 +20,9 @@ export default function RootLayout({
2120
<html lang="en">
2221
<NoSSR>
2322
<SessionProvider>
24-
<body className={inter.className}>{children}</body>
23+
<TrpcProvider>
24+
<body>{children}</body>
25+
</TrpcProvider>
2526
</SessionProvider>
2627
</NoSSR>
2728
</html>

apps/webapp/src/app/trpc-provider.tsx

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2+
import { trpc } from "@/utils/trpc";
3+
import { httpBatchLink } from "@trpc/client";
4+
import { useState } from "react";
5+
6+
export function TrpcProvider({ children }: { children: React.ReactNode }) {
7+
const [queryClient] = useState(() => new QueryClient());
8+
const [trpcClient] = useState(() =>
9+
trpc.createClient({
10+
links: [
11+
httpBatchLink({
12+
url: `${getBaseUrl()}/api/trpc`,
13+
// You can pass any HTTP headers you wish here
14+
async headers() {
15+
return {
16+
// authorization: getAuthCookie(),
17+
};
18+
},
19+
}),
20+
],
21+
}),
22+
);
23+
24+
return (
25+
<trpc.Provider client={trpcClient} queryClient={queryClient}>
26+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
27+
</trpc.Provider>
28+
);
29+
}
30+
31+
function getBaseUrl() {
32+
if (typeof window !== "undefined")
33+
// browser should use relative path
34+
return "";
35+
if (process.env.VERCEL_URL)
36+
// reference for vercel.com
37+
return `https://${process.env.VERCEL_URL}`;
38+
if (process.env.RENDER_INTERNAL_HOSTNAME)
39+
// reference for render.com
40+
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
41+
// assume localhost
42+
return `http://localhost:${process.env.PORT ?? 3000}`;
43+
}

apps/webapp/src/lib/server/auth-options.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { DrizzleAdapter } from "@auth/drizzle-adapter";
22
import { db } from "@/server/db";
3-
import EmailProvider from "next-auth/providers/email";
4-
import GithubProvider from "next-auth/providers/github";
53
import { NextAuthOptions } from "next-auth";
4+
import GithubProvider from "next-auth/providers/github";
65

76
export const authOptions: NextAuthOptions = {
87
adapter: DrizzleAdapter(db),
9-
providers: [EmailProvider({})],
8+
providers: [
9+
GithubProvider({
10+
clientId: process.env.GITHUB_CLIENT_ID!,
11+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
12+
}),
13+
],
1014
callbacks: {
1115
session: ({ session, user }) => ({
1216
...session,

apps/webapp/src/server/schema.ts

+82-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { AdapterAccount } from "next-auth/adapters";
12
import {
23
pgTable,
34
serial,
@@ -6,6 +7,8 @@ import {
67
integer,
78
uniqueIndex,
89
pgEnum,
10+
boolean,
11+
primaryKey,
912
} from "drizzle-orm/pg-core";
1013

1114
export const approvalRequestStatusEnum = pgEnum("approval_request_status", [
@@ -14,16 +17,86 @@ export const approvalRequestStatusEnum = pgEnum("approval_request_status", [
1417
"rejected",
1518
]);
1619

17-
export const users = pgTable("users", {
18-
id: serial("id").primaryKey(),
19-
name: text("name").notNull(),
20-
email: text("email").notNull().unique(),
21-
createdAt: timestamp("created_at").defaultNow().notNull(),
20+
export const users = pgTable("user", {
21+
id: text("id")
22+
.primaryKey()
23+
.$defaultFn(() => crypto.randomUUID()),
24+
name: text("name"),
25+
email: text("email").unique(),
26+
emailVerified: timestamp("emailVerified", { mode: "date" }),
27+
image: text("image"),
28+
});
29+
30+
export const accounts = pgTable(
31+
"account",
32+
{
33+
userId: text("userId")
34+
.notNull()
35+
.references(() => users.id, { onDelete: "cascade" }),
36+
type: text("type").$type<AdapterAccount["type"]>().notNull(),
37+
provider: text("provider").notNull(),
38+
providerAccountId: text("providerAccountId").notNull(),
39+
refresh_token: text("refresh_token"),
40+
access_token: text("access_token"),
41+
expires_at: integer("expires_at"),
42+
token_type: text("token_type"),
43+
scope: text("scope"),
44+
id_token: text("id_token"),
45+
session_state: text("session_state"),
46+
},
47+
(account) => ({
48+
compoundKey: primaryKey({
49+
columns: [account.provider, account.providerAccountId],
50+
}),
51+
}),
52+
);
53+
54+
export const sessions = pgTable("session", {
55+
sessionToken: text("sessionToken").primaryKey(),
56+
userId: text("userId")
57+
.notNull()
58+
.references(() => users.id, { onDelete: "cascade" }),
59+
expires: timestamp("expires", { mode: "date" }).notNull(),
2260
});
2361

62+
export const verificationTokens = pgTable(
63+
"verificationToken",
64+
{
65+
identifier: text("identifier").notNull(),
66+
token: text("token").notNull(),
67+
expires: timestamp("expires", { mode: "date" }).notNull(),
68+
},
69+
(verificationToken) => ({
70+
compositePk: primaryKey({
71+
columns: [verificationToken.identifier, verificationToken.token],
72+
}),
73+
}),
74+
);
75+
76+
export const authenticators = pgTable(
77+
"authenticator",
78+
{
79+
credentialID: text("credentialID").notNull().unique(),
80+
userId: text("userId")
81+
.notNull()
82+
.references(() => users.id, { onDelete: "cascade" }),
83+
providerAccountId: text("providerAccountId").notNull(),
84+
credentialPublicKey: text("credentialPublicKey").notNull(),
85+
counter: integer("counter").notNull(),
86+
credentialDeviceType: text("credentialDeviceType").notNull(),
87+
credentialBackedUp: boolean("credentialBackedUp").notNull(),
88+
transports: text("transports"),
89+
},
90+
(authenticator) => ({
91+
compositePK: primaryKey({
92+
columns: [authenticator.userId, authenticator.credentialID],
93+
}),
94+
}),
95+
);
96+
2497
export const devices = pgTable("devices", {
2598
id: serial("id").primaryKey(),
26-
userId: integer("user_id")
99+
userId: text("user_id")
27100
.references(() => users.id)
28101
.notNull(),
29102
name: text("name").notNull(),
@@ -46,7 +119,7 @@ export const packageMembers = pgTable(
46119
packageId: integer("package_id")
47120
.references(() => packages.id)
48121
.notNull(),
49-
userId: integer("user_id")
122+
userId: text("user_id")
50123
.references(() => users.id)
51124
.notNull(),
52125
createdAt: timestamp("created_at").defaultNow().notNull(),
@@ -77,7 +150,7 @@ export const approvalGroupMembers = pgTable(
77150
groupId: integer("group_id")
78151
.references(() => approvalGroups.id)
79152
.notNull(),
80-
userId: integer("user_id")
153+
userId: text("user_id")
81154
.references(() => users.id)
82155
.notNull(),
83156
createdAt: timestamp("created_at").defaultNow().notNull(),
@@ -109,7 +182,7 @@ export const approvals = pgTable(
109182
requestId: integer("request_id")
110183
.references(() => approvalRequests.id)
111184
.notNull(),
112-
userId: integer("user_id")
185+
userId: text("user_id")
113186
.references(() => users.id)
114187
.notNull(),
115188
createdAt: timestamp("created_at").defaultNow().notNull(),

apps/webapp/src/server/trpc.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ export const protectedProcedure = t.procedure.use(async (opts) => {
2525
return opts.next({
2626
ctx: {
2727
...ctx,
28-
user: ctx.session.user,
28+
user: {
29+
...ctx.session.user,
30+
id: (ctx.session.user as any).id as string,
31+
},
2932
},
3033
});
3134
});

apps/webapp/src/utils/trpc.ts

+2-41
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,6 @@
11
"use client";
22

33
import { AppRouter } from "@/server/router";
4-
import { createTRPCNext } from "@trpc/next";
5-
import { httpBatchLink } from "@trpc/client";
4+
import { createTRPCReact } from "@trpc/react-query";
65

7-
function getBaseUrl() {
8-
if (typeof window !== "undefined")
9-
// browser should use relative path
10-
return "";
11-
if (process.env.VERCEL_URL)
12-
// reference for vercel.com
13-
return `https://${process.env.VERCEL_URL}`;
14-
if (process.env.RENDER_INTERNAL_HOSTNAME)
15-
// reference for render.com
16-
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
17-
// assume localhost
18-
return `http://localhost:${process.env.PORT ?? 3000}`;
19-
}
20-
21-
export const trpc = createTRPCNext<AppRouter>({
22-
config() {
23-
return {
24-
links: [
25-
httpBatchLink({
26-
/**
27-
* If you want to use SSR, you need to use the server's full URL
28-
* @see https://trpc.io/docs/v11/ssr
29-
**/
30-
url: `${getBaseUrl()}/api/trpc`,
31-
// You can pass any HTTP headers you wish here
32-
async headers() {
33-
return {
34-
// authorization: getAuthCookie(),
35-
};
36-
},
37-
}),
38-
],
39-
};
40-
},
41-
/**
42-
* @see https://trpc.io/docs/v11/ssr
43-
**/
44-
ssr: false,
45-
});
6+
export const trpc = createTRPCReact<AppRouter>();

0 commit comments

Comments
 (0)