Skip to content

Commit

Permalink
feat(users): Add public.users table and new users to it
Browse files Browse the repository at this point in the history
Add public.users table because supabase auth.users is private.
Add users to it after sign up.
Handle database migrations and queries with prisma.
Add prisma (CLI) and @prisma/client dependencies.
  • Loading branch information
flsilva committed Jul 26, 2023
1 parent 9d1d58a commit 0df2894
Show file tree
Hide file tree
Showing 14 changed files with 319 additions and 9 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: deploy
run-name: Reusable deploy workflow to deploy database changes to the target Supabase database.
on:
workflow_call:
secrets:
DATABASE_URL:
required: true
type: string
DATABASE_DIRECT_URL:
required: true
type: string
jobs:
prisma-migrate-deploy:
name: Run prisma migrations
runs-on: ubuntu-latest
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ secrets.DATABASE_DIRECT_URL }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
cache: 'npm'
node-version-file: '.nvmrc'
- run: npx prisma@5.0.0 migrate deploy
15 changes: 15 additions & 0 deletions .github/workflows/preview-deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: staging-deploy
run-name: Deploy database changes to Supabase's Staging database.
on:
push:
branches-ignore:
- 'main'
jobs:
call-deploy-workflow:
name: Call reusable deploy workflow
environment:
name: staging
uses: ./.github/workflows/deploy.yaml
secrets:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ secrets.DATABASE_DIRECT_URL }}
15 changes: 15 additions & 0 deletions .github/workflows/production-deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: production-deploy
run-name: Deploy database changes to Supabase's Production database.
on:
push:
branches-ignore:
- 'main'
jobs:
call-deploy-workflow:
name: Call reusable deploy workflow
environment:
name: production
uses: ./.github/workflows/deploy.yaml
secrets:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ secrets.DATABASE_DIRECT_URL }}
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "prisma generate && next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"postinstall": "prisma generate"
},
"dependencies": {
"@headlessui/react": "1.7.15",
"@prisma/client": "5.0.0",
"@supabase/auth-helpers-nextjs": "0.7.2",
"@supabase/supabase-js": "2.26.0",
"@tailwindcss/forms": "0.5.3",
Expand All @@ -35,6 +37,7 @@
"typescript": "5.1.3"
},
"devDependencies": {
"prisma": "5.0.0",
"supabase": "1.77.9"
}
}
17 changes: 17 additions & 0 deletions prisma/migrations/20230721144417_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "User" (
"id" UUID NOT NULL,
"email" VARCHAR(254) NOT NULL,
"name" VARCHAR(500),
"provider" VARCHAR(100) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3),

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

-- CreateIndex
CREATE UNIQUE INDEX "User_provider_key" ON "User"("provider");
3 changes: 3 additions & 0 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
21 changes: 21 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_DIRECT_URL")
}

generator client {
provider = "prisma-client-js"
}

// We use Supabase's authentication solution, which create users in a private "auth" schema.
// So we create this User table in our "public" schema and add users to it after
// they're created by Supabase's auth solution, using the same "id".
model User {
id String @id @db.Uuid
email String @unique @db.VarChar(254)
name String? @db.VarChar(500)
provider String @unique @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
}
24 changes: 24 additions & 0 deletions src/app/app/shared/user/user-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PrismaClient, User } from '@prisma/client';

export interface CreateUserData {
readonly email: string;
readonly id: string;
readonly name?: string;
readonly provider: string;
}

export const createUser = async (data: CreateUserData) => {
const prisma = new PrismaClient();

const user = await prisma.user.findUnique({ where: { id: data.id } });

if (user) return user;

const newUser = await prisma.user.create({ data });
return newUser;
};

export const findUserById = async (id: string) => {
const prisma = new PrismaClient();
return await prisma.user.findUnique({ where: { id } });
};
16 changes: 16 additions & 0 deletions src/app/auth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NextResponse } from 'next/server';

import type { NextRequest } from 'next/server';
import type { Database } from '@/lib/database.types';
import { createUser } from '@/app/app/shared/user/user-model';

export async function GET(request: NextRequest) {
const requestUrl = new URL(request.url);
Expand All @@ -12,6 +13,21 @@ export async function GET(request: NextRequest) {
if (code) {
const supabase = createRouteHandlerClient<Database>({ cookies });
await supabase.auth.exchangeCodeForSession(code);

const {
data: { session },
} = await supabase.auth.getSession();

if (session) {
const { user } = session;

createUser({
id: user.id,
email: user.email as string,
name: user.user_metadata.full_name as string,
provider: user.app_metadata.provider as string,
});
}
}

// URL to redirect to after sign in process completes
Expand Down
6 changes: 2 additions & 4 deletions src/app/auth/sign-in/check-email-link/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ export default function CheckEmailLink({ searchParams }: { searchParams: { email
<h2 className="mt-10 text-2xl font-medium text-black md:text-3xl">Please check your email</h2>
<div className="flex flex-col items-center sm:mx-auto sm:w-full sm:max-w-sm">
<MailIcon width="5rem" height="5rem" className="my-6 md:my-10" />
<p className="mb-6 text-center">You&apos;re almost there!</p>
<p className="mb-6 text-center">
We just emailed a link to {getEmailText()}. Click the link, and you&apos;ll be signed in.
</p>
<p className="mb-2 text-center">We just emailed a link to {getEmailText()}.</p>
<p className="mb-6 text-center">Click on it, and you&apos;ll be signed in.</p>
<p className="text-center">
If you don&apos;t see it, you may need to{' '}
<span className="font-semibold">check your spam folder</span>.
Expand Down
6 changes: 4 additions & 2 deletions src/app/auth/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ interface OAuthProviderButtonProps extends ChildrenProps {
}

export default function SignIn() {
const redirectTo = `${process.env.NEXT_PUBLIC_VERCEL_URL}/auth/callback`;

const signInWithEmailHandler = async (formData: FormData) => {
'use server';
const email = String(formData.get('email'));
Expand All @@ -29,7 +31,7 @@ export default function SignIn() {
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
emailRedirectTo: redirectTo,
},
});

Expand All @@ -50,7 +52,7 @@ export default function SignIn() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
redirectTo,
},
});

Expand Down
61 changes: 60 additions & 1 deletion src/lib/database.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,66 @@ export interface Database {
}
public: {
Tables: {
[_ in never]: never
_prisma_migrations: {
Row: {
applied_steps_count: number
checksum: string
finished_at: string | null
id: string
logs: string | null
migration_name: string
rolled_back_at: string | null
started_at: string
}
Insert: {
applied_steps_count?: number
checksum: string
finished_at?: string | null
id: string
logs?: string | null
migration_name: string
rolled_back_at?: string | null
started_at?: string
}
Update: {
applied_steps_count?: number
checksum?: string
finished_at?: string | null
id?: string
logs?: string | null
migration_name?: string
rolled_back_at?: string | null
started_at?: string
}
Relationships: []
}
User: {
Row: {
createdAt: string
email: string
id: string
name: string | null
provider: string
updatedAt: string | null
}
Insert: {
createdAt?: string
email: string
id: string
name?: string | null
provider: string
updatedAt?: string | null
}
Update: {
createdAt?: string
email?: string
id?: string
name?: string | null
provider?: string
updatedAt?: string | null
}
Relationships: []
}
}
Views: {
[_ in never]: never
Expand Down
Loading

0 comments on commit 0df2894

Please sign in to comment.