├── .eslintrc.json
├── next.config.js
├── postcss.config.js
├── src
├── app
│ ├── api
│ │ └── auth
│ │ │ └── [...nextauth]
│ │ │ └── route.ts
│ ├── page.tsx
│ ├── layout.tsx
│ ├── globals.css
│ └── favicon.ico
├── types
│ └── next-auth.d.ts
├── drizzle
│ ├── index.ts
│ ├── prepared
│ │ └── index.ts
│ ├── schema
│ │ └── auth.ts
│ └── adapter
│ │ └── index.ts
├── components
│ └── Button.tsx
└── lib
│ └── auth.ts
├── drizzle.config.ts
├── .gitignore
├── tailwind.config.js
├── public
├── vercel.svg
└── next.svg
├── tsconfig.json
├── package.json
├── README.md
├── .github
└── workflows
│ └── ci.yml
└── pnpm-lock.yaml
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | module.exports = nextConfig;
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth";
2 | import { authOptions } from "~/lib/auth";
3 |
4 | const handler = NextAuth(authOptions);
5 |
6 | export { handler as GET, handler as POST };
7 |
--------------------------------------------------------------------------------
/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "drizzle-kit";
2 | import "dotenv/config";
3 |
4 | const databaseURL = process.env["DATABASE_URL"];
5 | if (!databaseURL) throw new Error("Enviromental database Url not");
6 |
7 | const config: Config = {
8 | schema: ["./src/drizzle/schema/*"],
9 | out: "./src/drizzle/migrations",
10 | driver: "mysql2",
11 | dbCredentials: {
12 | connectionString: databaseURL,
13 | },
14 | };
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/src/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import type { Session, User } from "next-auth"; // eslint-disable-line
2 | import type { JWT } from "next-auth/jwt"; // eslint-disable-line
3 |
4 | declare module "next-auth/jwt" {
5 | interface JWT {
6 | id: string;
7 | name: string;
8 | email: string;
9 | }
10 | }
11 |
12 | declare module "next-auth" {
13 | interface Session {
14 | user: User & {
15 | id: string;
16 | name: string;
17 | email: string;
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "~/components/Button";
2 | import { getAuthSession } from "~/lib/auth";
3 |
4 | export default async function Home() {
5 | const session = await getAuthSession();
6 | return (
7 |
8 |
9 | User logged in: {session?.user ? "true" : "false"}
10 | {session?.user && {session.user.email}
}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/src/drizzle/index.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from "drizzle-orm/planetscale-serverless";
2 | import { connect } from "@planetscale/database";
3 | import "dotenv/config";
4 |
5 | import * as AuthSchema from "./schema/auth";
6 |
7 | const databaseURL = process.env["DATABASE_URL"];
8 | if (!databaseURL) throw new Error("Enviromental database Url not");
9 |
10 | const connection = connect({
11 | url: databaseURL,
12 | });
13 |
14 | export const adapterDB = drizzle(connection);
15 | export const db = drizzle(connection, {
16 | schema: AuthSchema,
17 | });
18 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 | import type { Metadata } from "next";
3 | import { Inter } from "next/font/google";
4 |
5 | const inter = Inter({ subsets: ["latin"] });
6 |
7 | export const metadata: Metadata = {
8 | title: "Create Next App",
9 | description: "Generated by create next app",
10 | };
11 |
12 | export default function RootLayout({
13 | children,
14 | }: {
15 | children: React.ReactNode;
16 | }) {
17 | return (
18 |
19 |
{children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
7 | ],
8 | theme: {
9 | extend: {
10 | backgroundImage: {
11 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
12 | "gradient-conic":
13 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
14 | },
15 | },
16 | },
17 | plugins: [],
18 | };
19 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Session } from "next-auth";
3 | import { signIn, signOut } from "next-auth/react";
4 |
5 | interface ButtonProps {
6 | session: Session | null;
7 | }
8 |
9 | export const Button = ({ session }: ButtonProps) => {
10 | const handleUser = (e: React.MouseEvent) => {
11 | e.preventDefault();
12 | if (session?.user) {
13 | signOut();
14 | } else {
15 | signIn("discord");
16 | }
17 | };
18 | return (
19 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "checkJs": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "noEmit": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "incremental": true,
18 | "noUncheckedIndexedAccess": true,
19 | "baseUrl": ".",
20 | "paths": {
21 | "~/*": ["./src/*"]
22 | },
23 | "plugins": [
24 | {
25 | "name": "next"
26 | }
27 | ]
28 | },
29 | "include": [
30 | ".eslintrc.cjs",
31 | "**/*.d.ts",
32 | "**/*.ts",
33 | "**/*.tsx",
34 | "**/*.cjs",
35 | "**/*.mjs",
36 | ".next/types/**/*.ts"
37 | ],
38 | "exclude": ["node_modules"]
39 | }
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ms5/drizzle-nextauth-planetscale",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "type-check": "tsc -p tsconfig.json --noEmit",
11 | "test": "pnpm lint && pnpm type-check && pnpm build",
12 | "db:push": "drizzle-kit push:mysql --config=drizzle.config.ts",
13 | "db:studio": "drizzle-kit studio --config=drizzle.config.ts --port 4983 --verbose"
14 | },
15 | "dependencies": {
16 | "@planetscale/database": "^1.10.0",
17 | "@types/node": "20.4.5",
18 | "@types/react": "18.2.17",
19 | "@types/react-dom": "18.2.7",
20 | "autoprefixer": "10.4.14",
21 | "dotenv": "^16.3.1",
22 | "drizzle-orm": "^0.27.2",
23 | "eslint": "8.46.0",
24 | "eslint-config-next": "13.4.12",
25 | "next": "13.4.12",
26 | "next-auth": "^4.22.3",
27 | "postcss": "8.4.27",
28 | "react": "18.2.0",
29 | "react-dom": "18.2.0",
30 | "tailwindcss": "3.3.3",
31 | "typescript": "5.1.6"
32 | },
33 | "devDependencies": {
34 | "drizzle-kit": "^0.19.12"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/drizzle/prepared/index.ts:
--------------------------------------------------------------------------------
1 | import { placeholder } from "drizzle-orm";
2 | import { db } from "..";
3 |
4 | export const runtime = "edge";
5 |
6 | export const pGetUserByEmail = db.query.user
7 | .findFirst({
8 | where: (user, { eq }) => eq(user.email, placeholder("email")),
9 | })
10 | .prepare();
11 |
12 | export const pGetUserById = db.query.user
13 | .findFirst({
14 | where: (user, { eq }) => eq(user.id, placeholder("id")),
15 | })
16 | .prepare();
17 |
18 | export const pGetUserByAccount = db.query.account
19 | .findFirst({
20 | where: (account, { eq, and }) =>
21 | and(
22 | eq(account.providerAccountId, placeholder("providerAccountId")),
23 | eq(account.provider, placeholder("provider"))
24 | ),
25 | with: {
26 | user: true,
27 | },
28 | })
29 | .prepare();
30 |
31 | export const pGetSessionByToken = db.query.session
32 | .findFirst({
33 | where: (session, { eq }) =>
34 | eq(session.sessionToken, placeholder("sessionToken")),
35 | })
36 | .prepare();
37 |
38 | export const pGetSessionAndUser = db.query.session
39 | .findFirst({
40 | where: (session, { eq }) =>
41 | eq(session.sessionToken, placeholder("sessionToken")),
42 | with: {
43 | user: true,
44 | },
45 | })
46 | .prepare();
47 |
48 | export const pGetVerificationTokenByToken = db.query.verificationToken
49 | .findFirst({
50 | where: (vt, { eq }) => eq(vt.token, placeholder("token")),
51 | })
52 | .prepare();
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
18 |
19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | pull_request:
7 | types: [opened, synchronize]
8 |
9 | jobs:
10 | test-build:
11 | name: Test and building
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Check out repo
16 | uses: actions/checkout@v3
17 |
18 | - name: Set up Node.js 18
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: 18
22 |
23 | - name: Setup pnpm
24 | uses: pnpm/action-setup@v2.2.4
25 | with:
26 | version: 8.6.6
27 |
28 | - name: Get pnpm store directory
29 | id: pnpm-cache
30 | run: |
31 | echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
32 |
33 | - name: Setup pnpm cache
34 | uses: actions/cache@v3
35 | with:
36 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
38 | restore-keys: |
39 | ${{ runner.os }}-pnpm-store-
40 |
41 | - name: Install dependencies
42 | run: pnpm install
43 |
44 | - name: Building
45 | run: pnpm build
46 | env:
47 | DATABASE_URL: "https://localhost.com"
48 | NEXTAUTH_URL: "https://localhost.com"
49 | NEXTAUTH_SECRET: "abc123"
50 | DISCORD_CLIENT_ID: "abc123"
51 | DISCORD_CLIENT_SECRET: "abc123"
52 |
53 | - name: Linting
54 | run: pnpm lint
55 | env:
56 | DATABASE_URL: "https://localhost.com"
57 | NEXTAUTH_URL: "https://localhost.com"
58 | NEXTAUTH_SECRET: "abc123"
59 | DISCORD_CLIENT_ID: "abc123"
60 | DISCORD_CLIENT_SECRET: "abc123"
61 |
62 | - name: Type-checking
63 | run: pnpm type-check
64 |
--------------------------------------------------------------------------------
/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { getServerSession, type NextAuthOptions } from "next-auth";
2 | import DiscordProvider from "next-auth/providers/discord";
3 | import { adapterDB, db } from "~/drizzle";
4 | import "dotenv/config";
5 | import { DrizzleAdapter } from "~/drizzle/adapter";
6 | import * as AuthSchema from "~/drizzle/schema/auth";
7 | import {
8 | pGetSessionAndUser,
9 | pGetSessionByToken,
10 | pGetUserByAccount,
11 | pGetUserByEmail,
12 | pGetUserById,
13 | pGetVerificationTokenByToken,
14 | } from "~/drizzle/prepared";
15 |
16 | const NEXTAUTH_SECRET = process.env["NEXTAUTH_SECRET"];
17 | const DISCORD_CLIENT_ID = process.env["DISCORD_CLIENT_ID"];
18 | const DISCORD_CLIENT_SECRET = process.env["DISCORD_CLIENT_SECRET"];
19 | if (!NEXTAUTH_SECRET)
20 | throw new Error("NEXTAUTH_SECRET is missing from env variables");
21 | if (!DISCORD_CLIENT_ID)
22 | throw new Error("DISCORD_CLIENT_ID is missing from env variables");
23 | if (!DISCORD_CLIENT_SECRET)
24 | throw new Error("DISCORD_CLIENT_SECRET is missing from env variables");
25 |
26 | export const authOptions: NextAuthOptions = {
27 | adapter: DrizzleAdapter(adapterDB, {
28 | schemas: {
29 | account: AuthSchema.account,
30 | session: AuthSchema.session,
31 | user: AuthSchema.user,
32 | verificationToken: AuthSchema.verificationToken,
33 | },
34 | prepared: {
35 | getUserByEmail: pGetUserByEmail,
36 | getUserById: pGetUserById,
37 | getUserByAccount: pGetUserByAccount,
38 | getSessionByToken: pGetSessionByToken,
39 | getSessionAndUser: pGetSessionAndUser,
40 | getVerificationTokenByToken: pGetVerificationTokenByToken,
41 | },
42 | }),
43 | secret: NEXTAUTH_SECRET,
44 | session: {
45 | strategy: "jwt",
46 | },
47 | providers: [
48 | DiscordProvider({
49 | clientId: DISCORD_CLIENT_ID,
50 | clientSecret: DISCORD_CLIENT_SECRET,
51 | }),
52 | ],
53 | callbacks: {
54 | session: ({ session, token }) => {
55 | if (token) {
56 | session.user.id = token.id;
57 | session.user.name = token.name;
58 | session.user.email = token.email;
59 | session.user.image = token.picture;
60 | }
61 |
62 | return session;
63 | },
64 |
65 | jwt: async ({ token, user }) => {
66 | const dbUser = await db.query.user.findFirst({
67 | where: (user, { eq }) => eq(user.email, token.email),
68 | });
69 |
70 | if (!dbUser) {
71 | if (user) {
72 | token.id = user.id;
73 | }
74 | return token;
75 | }
76 |
77 | return {
78 | id: dbUser.id,
79 | name: dbUser.name || "",
80 | email: dbUser.email,
81 | picture: dbUser.image,
82 | };
83 | },
84 | },
85 | };
86 |
87 | export const getAuthSession = () => getServerSession(authOptions);
88 |
--------------------------------------------------------------------------------
/src/drizzle/schema/auth.ts:
--------------------------------------------------------------------------------
1 | import { relations } from "drizzle-orm";
2 | import {
3 | datetime,
4 | int,
5 | mysqlTable,
6 | primaryKey,
7 | text,
8 | timestamp,
9 | uniqueIndex,
10 | varchar,
11 | } from "drizzle-orm/mysql-core";
12 |
13 | export const account = mysqlTable(
14 | "account",
15 | {
16 | id: varchar("id", { length: 191 }).notNull().primaryKey(),
17 | userId: varchar("user_id", { length: 191 }).notNull(),
18 | type: varchar("type", { length: 191 }).notNull(),
19 | provider: varchar("provider", { length: 191 }).notNull(),
20 | providerAccountId: varchar("provider_account_id", {
21 | length: 191,
22 | }).notNull(),
23 | refreshToken: text("refresh_token"),
24 | accessToken: text("access_token"),
25 | expiresAt: int("expires_at"),
26 | tokenType: varchar("token_type", { length: 191 }),
27 | scope: varchar("scope", { length: 191 }),
28 | idToken: text("id_token"),
29 | sessionState: varchar("session_state", { length: 191 }),
30 | createdAt: timestamp("created_at").notNull().defaultNow(),
31 | updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
32 | },
33 | (account) => ({
34 | providerIndex: uniqueIndex("provider_idx").on(
35 | account.provider,
36 | account.providerAccountId
37 | ),
38 | })
39 | );
40 |
41 | export const session = mysqlTable(
42 | "session",
43 | {
44 | userId: varchar("user_id", { length: 191 }).notNull(),
45 | expires: datetime("expires").notNull(),
46 | sessionToken: varchar("session_token", { length: 191 }).notNull(),
47 | },
48 | (session) => ({
49 | sessionTokenIndex: primaryKey(session.sessionToken),
50 | })
51 | );
52 |
53 | export const verificationToken = mysqlTable(
54 | "verification_token",
55 | {
56 | identifier: varchar("identifier", { length: 191 }).notNull(),
57 | token: varchar("token", { length: 191 }).notNull(),
58 | expires: datetime("expires").notNull(),
59 | },
60 | (request) => ({
61 | identifierTokenIndex: uniqueIndex("identifier_token_idx").on(
62 | request.identifier,
63 | request.token
64 | ),
65 | })
66 | );
67 |
68 | export const user = mysqlTable(
69 | "user",
70 | {
71 | id: varchar("id", { length: 191 }).notNull().primaryKey(),
72 | name: varchar("name", { length: 191 }),
73 | email: varchar("email", { length: 191 }).notNull(),
74 | emailVerified: timestamp("email_verified"),
75 | image: varchar("image", { length: 191 }),
76 | createdAt: timestamp("created_at").notNull().defaultNow(),
77 | updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
78 | },
79 | (user) => ({
80 | emailIndex: uniqueIndex("email_idx").on(user.email),
81 | })
82 | );
83 |
84 | export const userRelations = relations(user, ({ many, one }) => ({
85 | account: many(account),
86 | session: many(session),
87 | }));
88 |
89 | export const accountRelations = relations(account, ({ one }) => ({
90 | user: one(user, {
91 | fields: [account.userId],
92 | references: [user.id],
93 | }),
94 | }));
95 |
96 | export const sessionRelations = relations(session, ({ one }) => ({
97 | user: one(user, {
98 | fields: [session.userId],
99 | references: [user.id],
100 | }),
101 | }));
102 |
--------------------------------------------------------------------------------
/src/drizzle/adapter/index.ts:
--------------------------------------------------------------------------------
1 | import { and, eq } from "drizzle-orm";
2 | import type { Adapter, AdapterAccount, AdapterUser } from "next-auth/adapters";
3 | import {
4 | PlanetScaleDatabase,
5 | PlanetScalePreparedQuery,
6 | } from "drizzle-orm/planetscale-serverless";
7 | import {
8 | MySqlTableWithColumns,
9 | PreparedQueryConfig,
10 | } from "drizzle-orm/mysql-core";
11 |
12 | type PreparedStatement = PlanetScalePreparedQuery<
13 | PreparedQueryConfig & {
14 | execute: T | undefined;
15 | }
16 | >;
17 |
18 | interface DrizzleAdapterConfig {
19 | schemas: {
20 | user: MySqlTableWithColumns;
21 | account: MySqlTableWithColumns;
22 | session: MySqlTableWithColumns;
23 | verificationToken: MySqlTableWithColumns;
24 | };
25 | prepared?: {
26 | getUserByEmail?: PreparedStatement;
27 | getUserById?: PreparedStatement;
28 | getUserByAccount?: PreparedStatement;
29 | getSessionByToken?: PreparedStatement;
30 | getSessionAndUser?: PreparedStatement;
31 | getVerificationTokenByToken?: PreparedStatement;
32 | };
33 | }
34 |
35 | export const DrizzleAdapter = (
36 | db: PlanetScaleDatabase,
37 | config: DrizzleAdapterConfig
38 | ): Adapter => {
39 | const s = config.schemas;
40 | const p = config.prepared;
41 | return {
42 | createUser: async (userData) => {
43 | const id = crypto.randomUUID();
44 | await db.insert(s.user).values({
45 | id,
46 | ...userData,
47 | });
48 |
49 | const user = p?.getUserById
50 | ? await p.getUserById.execute({ id })
51 | : await db
52 | .select()
53 | .from(s.user)
54 | .where(eq(s.user.id, id))
55 | .then((res) => res[0]);
56 | if (!user) throw new Error("User not found");
57 | return user;
58 | },
59 |
60 | getUser: async (id) => {
61 | const user = p?.getUserById
62 | ? await p.getUserById.execute({ id })
63 | : await db
64 | .select()
65 | .from(s.user)
66 | .where(eq(s.user.id, id))
67 | .then((res) => res[0]);
68 | return user ?? null;
69 | },
70 |
71 | getUserByEmail: async (email) => {
72 | const user = p?.getUserByEmail
73 | ? await p.getUserByEmail.execute({ email })
74 | : await db
75 | .select()
76 | .from(s.user)
77 | .where(eq(s.user.email, email))
78 | .then((res) => res[0]);
79 | return user ?? null;
80 | },
81 |
82 | getUserByAccount: async ({ provider, providerAccountId }) => {
83 | const account = p?.getUserByAccount
84 | ? await p.getUserByAccount.execute({ provider, providerAccountId })
85 | : await db
86 | .select()
87 | .from(s.account)
88 | .where(
89 | and(
90 | eq(s.account.provider, provider),
91 | eq(s.account.providerAccountId, providerAccountId)
92 | )
93 | )
94 | .leftJoin(s.user, eq(s.account.userId, s.user.id))
95 | .then((res) => res[0]);
96 | return account?.user ?? null;
97 | },
98 |
99 | updateUser: async ({ id, ...data }) => {
100 | await db.update(s.user).set(data).where(eq(s.user.id, id));
101 | const user = p?.getUserById
102 | ? await p.getUserById.execute({ id })
103 | : await db
104 | .select()
105 | .from(s.user)
106 | .where(eq(s.user.id, id))
107 | .then((res) => res[0]);
108 | return user;
109 | },
110 |
111 | deleteUser: async (id) => {
112 | await db.delete(s.user).where(eq(s.user.id, id));
113 | },
114 |
115 | linkAccount: async (account) => {
116 | await db.insert(s.account).values({
117 | id: crypto.randomUUID(),
118 | provider: account.provider,
119 | providerAccountId: account.providerAccountId,
120 | type: account.type,
121 | userId: account.userId,
122 | accessToken: account.access_token,
123 | expiresAt: account.expires_at,
124 | idToken: account.id_token,
125 | refreshToken: account.refresh_token,
126 | scope: account.scope,
127 | sessionState: account.session_state,
128 | tokenType: account.token_type,
129 | });
130 | },
131 |
132 | unlinkAccount: async ({ provider, providerAccountId }) => {
133 | await db
134 | .delete(s.account)
135 | .where(
136 | and(
137 | eq(s.account.providerAccountId, providerAccountId),
138 | eq(s.account.provider, provider)
139 | )
140 | );
141 | },
142 |
143 | createSession: async (data) => {
144 | await db.insert(s.session).values(data);
145 | const session = p?.getSessionByToken
146 | ? await p.getSessionByToken.execute({ sessionToken: data.sessionToken })
147 | : await db
148 | .select()
149 | .from(s.session)
150 | .where(eq(s.session.sessionToken, data.sessionToken))
151 | .then((res) => res[0]);
152 | console.log("createSession", session);
153 | return session;
154 | },
155 |
156 | getSessionAndUser: async (sessionToken) => {
157 | const data = p?.getSessionAndUser
158 | ? await p.getSessionAndUser.execute({ sessionToken })
159 | : await db
160 | .select({
161 | session: s.session,
162 | user: s.user,
163 | })
164 | .from(s.session)
165 | .where(eq(s.session.sessionToken, sessionToken))
166 | .innerJoin(s.user, eq(s.user.id, s.session.userId))
167 | .then((res) => res[0]);
168 |
169 | if (!data) return null;
170 | const { user, ...session } = data;
171 | return {
172 | user,
173 | session,
174 | };
175 | },
176 |
177 | updateSession: async (data) => {
178 | await db
179 | .update(s.session)
180 | .set(data)
181 | .where(eq(s.session.sessionToken, data.sessionToken));
182 | const session = p?.getSessionByToken
183 | ? await p.getSessionByToken.execute({
184 | sessionToken: data.sessionToken,
185 | })
186 | : await db
187 | .select()
188 | .from(s.session)
189 | .where(eq(s.session.sessionToken, data.sessionToken))
190 | .then((res) => res[0]);
191 | console.log("updateSession", session);
192 | return session ?? null;
193 | },
194 |
195 | deleteSession: async (sessionToken) => {
196 | await db
197 | .delete(s.session)
198 | .where(eq(s.session.sessionToken, sessionToken));
199 | },
200 |
201 | createVerificationToken: async (verificationToken) => {
202 | await db.insert(s.verificationToken).values(verificationToken);
203 | const token = p?.getVerificationTokenByToken
204 | ? await p.getVerificationTokenByToken.execute({
205 | token: verificationToken.token,
206 | })
207 | : await db
208 | .select()
209 | .from(s.verificationToken)
210 | .where(
211 | eq(s.verificationToken.identifier, verificationToken.identifier)
212 | )
213 | .then((res) => res[0]);
214 | console.log("createVerificationToken", token);
215 | return token ?? null;
216 | },
217 |
218 | useVerificationToken: async (verificationToken) => {
219 | const token = p?.getVerificationTokenByToken
220 | ? await p.getVerificationTokenByToken.execute({
221 | token: verificationToken.token,
222 | })
223 | : await db
224 | .select()
225 | .from(s.verificationToken)
226 | .where(
227 | eq(s.verificationToken.identifier, verificationToken.identifier)
228 | )
229 | .then((res) => res[0]);
230 | console.log("useVerificationToken", token);
231 | if (!token) return null;
232 | await db
233 | .delete(s.verificationToken)
234 | .where(
235 | and(
236 | eq(s.verificationToken.token, verificationToken.token),
237 | eq(s.verificationToken.identifier, verificationToken.identifier)
238 | )
239 | );
240 | return token;
241 | },
242 | };
243 | };
244 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
1 | ( F ( n 00 (- � � �F ( $ ] � � ] $ � � � � � � � � 8 � � � � � � � � � � 8 � � � � � � � � � � � � � � � � � � � � � � � � � � # � � �OOO�������������������������ggg� � � � # Y � � ��������������������������555� � � � Y � � � � �kkk��������������������� � � � � � � � � � � ������������������ � � � � � Y � � � � �JJJ���������kkk� � � � � � Y # � � � � ���������� � � � � � � # � � � � � �111�DDD� � � � � � � � � � � � � � � � � � � 8 � � � � � � � � � � 8 � � � � � � � � $ ] � � ] $ ( @ , U � � � � U , * � � � � � � � � � � � � * � � � � � � � � � � � � � � � � Q � � � � � � � � � � � � � � � � � � Q r � � � � � � � � � � � � � � � � � � � � r r � � � � � � � � � � � � � � � � � � � � � � r O � � � � � � � � � � � � � � � � � � � � � � � � O � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � ( � � � � � � � � � � � � � � � � � � � � � � � � � � � � ' � � � � � � �888���������������������������������������������������������___� � � � � � � � � � � � � � ����������������������������������������������������������SSS� � � � � � � � + � � � � � � � �hhh����������������������������������������������������� � � � � � � � � + T � � � � � � � ��������������������������������������������������,,,� � � � � � � � � T � � � � � � � � � �GGG��������������������������������������������� � � � � � � � � � � � � � � � � � � � � ������������������������������������������ � � � � � � � � � � � � � � � � � � � � �+++���������������������������������jjj� � � � � � � � � � � � � � � � � � � � � � � ���������������������������������� � � � � � � � � � � � T � � � � � � � � � � ��������������������������III� � � � � � � � � � � � T + � � � � � � � � � � � �hhh���������������������� � � � � � � � � � � � + � � � � � � � � � � � ������������������,,,� � � � � � � � � � � � � � � � � � � � � � � � � �GGG������������� � � � � � � � � � � � � � ' � � � � � � � � � � � � ���������� � � � � � � � � � � � � ( � � � � � � � � � � � � �333�___� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � O � � � � � � � � � � � � � � � � � � � � � � � � O r � � � � � � � � � � � � � � � � � � � � � � r r � � � � � � � � � � � � � � � � � � � � r Q � � � � � � � � � � � � � � � � � � Q � � � � � � � � � � � � � � � � * � � � � � � � � � � � � * , U � � � � U , ( 0 ` - ( L j � � � � j K ( V � � � � � � � � � � � � � � U % � � � � � � � � � � � � � � � � � � � � &