From 3ff7b0940f3141f98cd52f91228ef4652e198e4e Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 14:45:37 +0200 Subject: [PATCH 1/8] Remove init --- seed/supabase-twitter-clone/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index c2c77ab..884cf06 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -60,7 +60,6 @@ First, let's set up a local development environment for the Supabase Twitter clo ```bash npx supabase login - npx supabase init ``` ![supabase-init-asciinema](https://github.com/snaplet/examples/assets/8771783/10f11bca-5dd5-42ac-b81a-b33d6016026e) From be9096479de6762b299f3d1bc0b0b0bd697faa5b Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 14:49:10 +0200 Subject: [PATCH 2/8] rm mkdirp --- seed/supabase-twitter-clone/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index 884cf06..e2f1332 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -70,8 +70,7 @@ First, let's set up a local development environment for the Supabase Twitter clo # Your projectID can be found using the `supabase projects list` command and noting the REFERENCE ID value. # Input your remote database password when prompted. npx supabase link --project-ref - # Create a valid migrations folder for Supabase to pull the first migration. - mkdir -p supabase/migrations + # Pull the database schema from the remote project. npx supabase db pull ``` From fcc9ff6408d75b1a75032091734ee458943e5df5 Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 14:51:17 +0200 Subject: [PATCH 3/8] rm migration --- seed/supabase-twitter-clone/README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index e2f1332..70d947b 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -75,17 +75,6 @@ First, let's set up a local development environment for the Supabase Twitter clo npx supabase db pull ``` -This process creates a new `remote_schema.sql` file within the `supabase/migrations` folder. However, this migration lacks the necessary triggers and publications for our real-time updates to function correctly. Thus, we need to manually add them to the `remote_schema.sql` file: - -```sql --- Append at the end --- Trigger to create a profile for a user upon creation -CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION "public"."create_profile_for_user"(); --- Publication for the tweets table to enable real-time functionality -ALTER PUBLICATION "supabase_realtime" ADD TABLE "public"."tweets"; -RESET ALL; -``` - Next, we must synchronize our local development project with the remote one: ```bash From 48348d7f55577cff7600df6b7a502aa11607aecb Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 14:54:13 +0200 Subject: [PATCH 4/8] Rm user login code section --- seed/supabase-twitter-clone/README.md | 38 --------------------------- 1 file changed, 38 deletions(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index 70d947b..7d823aa 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -170,44 +170,6 @@ npm run dev Although OAuth login works, it's not the most efficient method for automating testing or quickly logging into different personas, as it would require multiple GitHub accounts. Let's address this issue next. -### Setup an Email+Password Login for Local Development - -For local development and testing, it's crucial to have the ability to log in as different personas easily. This can be achieved by creating a new user with pre-filled data. We can facilitate this by setting up an email and password login mechanism, and then utilize the Supabase admin interface to add specific data to it. - -Firstly, we'll create a utility route for development purposes. This route will allow us to easily log in as a user using an email and password. To accomplish this, create a new route at `app/auth/dev/login/route.ts` with the following content: - -```ts -import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; -import { NextResponse, type NextRequest } from "next/server"; -import { cookies } from "next/headers"; - -export const dynamic = "force-dynamic"; - -const inDevEnvironment = !!process && process.env.NODE_ENV === 'development'; - -export async function GET(request: NextRequest) { - // This route is intended for development/testing purposes only - if (!inDevEnvironment) { - return NextResponse.redirect('/') - } - const requestUrl = new URL(request.url); - // Extract email and password from query parameters - const email = requestUrl.searchParams.get("email"); - const password = requestUrl.searchParams.get("password"); - if (email && password) { - const supabase = createRouteHandlerClient({ cookies }); - // Sign in the user with email and password - await supabase.auth.signInWithPassword({ email, password }); - } - return NextResponse.redirect(requestUrl.origin); -} -``` - -With this setup, we can now easily log in as a user using email and password by navigating to: -`http://localhost:3000/api/auth/dev/login?email=&password=` - -However, we still need to create a new user with email and password. This is where Snaplet Seed will be utilized. - ### Setup @snaplet/seed To set it up: From 8f8bc4f5c3ed8eeef17dec0b2035e85a5ad73a45 Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 14:56:39 +0200 Subject: [PATCH 5/8] Typo --- seed/supabase-twitter-clone/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index 7d823aa..6dd0005 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -181,7 +181,7 @@ npx @snaplet/seed@latest init You will be asked to choose an "adapter" to connect to your local database, in this example we'll use "postgres-js". -The cli will genrate a default `seed.config.ts` for you and prompt you at some point +The cli will generate a default `seed.config.ts` for you and prompt you at some point to edit it to provide an "adapter" allowing us to connect to the database. What we need to do here is two things: From 607b5b7dbf043de8bc09a7053406ce6c5a172ff8 Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 15:03:54 +0200 Subject: [PATCH 6/8] Fix syntax error --- seed/supabase-twitter-clone/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index 6dd0005..67b1739 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -205,7 +205,7 @@ export default defineConfig({ alias: { // We want inflections name on our fields see: https://docs.snaplet.dev/seed/core-concepts#inflection inflection: true, - } + }, select: [ // We don't alter any extensions tables that might be owned by extensions "!*", From 80913dab4b6a521992c575bf5bba02ce2a98b557 Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 16:53:18 +0200 Subject: [PATCH 7/8] Base on what worked locally --- seed/supabase-twitter-clone/README.md | 102 +++++++++----------------- 1 file changed, 36 insertions(+), 66 deletions(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index 67b1739..1c58785 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -11,6 +11,7 @@ - [Setup OAuth for Local Development](#setup-oauth-for-local-development) - [Setup an Email+Password Login for Local Development](#setup-an-emailpassword-login-for-local-development) - [Setup @snaplet/seed](#setup-snapletseed) + - [Writing the Seed Script](#writing-the-seed-script) - [Snaplet Seed with E2E](#snaplet-seed-with-e2e) - [Conclusion](#conclusion) - [Acknowledgments](#acknowledgments) @@ -181,7 +182,7 @@ npx @snaplet/seed@latest init You will be asked to choose an "adapter" to connect to your local database, in this example we'll use "postgres-js". -The cli will generate a default `seed.config.ts` for you and prompt you at some point +The cli will genrate a default `seed.config.ts` for you and prompt you at some point to edit it to provide an "adapter" allowing us to connect to the database. What we need to do here is two things: @@ -205,7 +206,7 @@ export default defineConfig({ alias: { // We want inflections name on our fields see: https://docs.snaplet.dev/seed/core-concepts#inflection inflection: true, - }, + } select: [ // We don't alter any extensions tables that might be owned by extensions "!*", @@ -234,47 +235,18 @@ const seed = await createSeedClient(); // Reset the database, keeping the structure intact await seed.$resetDatabase() -// Create 3 records in the HttpResponses table -await seed.HttpResponses(x => x(3)) +// ... ``` -Now, let's edit our `seed.ts` file to generate some tweets: - -```ts -await seed.$resetDatabase() +### Writing the Seed Script -// Generate 10 tweets -await seed.tweets(x => x(10)) -``` - -After running `npx tsx seed.ts`, we encounter an error related to invalid `avatar_url` in the Next.js images. To fix this, we adjust the `avatar_url` generation in our `seed.ts`: +First we need to change `seed.ts` to create some users using the Supabase SDK. ```ts -import { faker } from '@snaplet/copycat'; - -const seed = await createSeedClient({ - models: { - profiles: { - data: { - avatarUrl: ({ seed }) => faker.image.avatarGitHub(), - } - } - } -}); - -await seed.$resetDatabase() - -// Generate 10 tweets with valid avatar URLs -await seed.tweets(x => x(10)) -``` - -We can now re-run our script with `npx tsx seed.ts`. - -Refreshing our page should now display the seeded tweet data correctly. - -To easily log in as the creators of these tweets, we integrate the Supabase SDK into our seed script: +import { createSeedClient } from '@snaplet/seed'; +import { copycat } from '@snaplet/copycat'; +import { createClient } from '@supabase/supabase-js'; -```ts const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, // Note you might want to use `SUPABASE_ROLE` key here with `auth.admin.signUp` if your app is using email confirmation @@ -282,6 +254,7 @@ const supabase = createClient( ); const PASSWORD = "testuser"; + for (let i = 0; i < 5; i++) { const email = copycat.email(i).toLowerCase(); const avatar = faker.image.avatarGitHub(); @@ -300,22 +273,25 @@ for (let i = 0; i < 5; i++) { } }); } +``` -const { data: databaseProfiles } = await supabase.from("profiles").select(); +This process creates a pool of 5 users with email and password logins, allowing us to easily log in as any tweet creator. It will also create the corresponding rows in the `profiles` table. -const profiles = databaseProfiles?.map(profile => ({ - avatarUrl: profile.avatar_url, - id: profile.id, - name: profile.name, - username: profile.username, -})) ?? []; +Now that we have this profile data, we can create some tweets using `@snaplet/seed`, and connect them up to these profiles: -// Insert tweets linked to profiles -await seed.tweets(x => x(10), { connect: { profiles } }); -console.log("Profiles created: ", profiles); -``` +```ts +// * In our app, all our data under public isn't directly linked under the auth.user table but rather under the +// public.profiles table. +// * For any user inserted in the auth.users table we have a trigger that will insert a row in the public.profiles table +// * Since `supabase.auth.signUp()` created a user, we should now have all the profiles created as well +const { data: databaseProfiles } = await supabase.from("profiles").select() -This process creates a pool of 5 users with email and password logins, allowing us to easily log in as any tweet creator. +// We convert our database fields to something that our seed client can understand +const profiles = databaseProfiles?.map(profile => ({ id: profile.id })) ?? []; + +// We can now use our seed client to insert tweets that will be linked to the profiles +await seed.tweets(x => x(10), {connect: { profiles }}) +``` Combining all the steps, our `seed.ts` file becomes: @@ -323,9 +299,9 @@ Combining all the steps, our `seed.ts` file becomes: Click to show the full code ```ts -import { createSeedClient, type profilesScalars } from '@snaplet/seed'; +import { createSeedClient } from '@snaplet/seed'; import { createClient } from '@supabase/supabase-js' -import {Database} from './lib/database.types' +import { Database } from './lib/database.types' import { copycat, faker } from '@snaplet/copycat' @@ -344,8 +320,6 @@ const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, ) - - // Clears all existing data in the database, but keep the structure await seed.$resetDatabase() @@ -367,26 +341,22 @@ for (let i = 0; i < 5; i += 1) { } }); } -// In our app, all our data under public isn't directly linked under the auth.user table but rather under the public.profiles table -// And for any user inserted in the auth.users table we have a trigger that will insert a row in the public.profiles table -// Since `supabase.auth.signUp` create a user, we should now have all the profiles created as well + +// * In our app, all our data under public isn't directly linked under the auth.user table but rather under the +// public.profiles table. +// * For any user inserted in the auth.users table we have a trigger that will insert a row in the public.profiles table +// * Since `supabase.auth.signUp()` created a user, we should now have all the profiles created as well const { data: databaseProfiles } = await supabase.from("profiles").select() -// We convert our database fields to something that our seed client can understand -const profiles: profilesScalars[] = databaseProfiles?.map(profile => ({ - avatarUrl: profile.avatar_url, - id: profile.id, - name: profile.name, - username: profile.username, -})) ?? [] + +// We convert our database fields to something that our seed client can understand +const profiles = databaseProfiles?.map(profile => ({ id: profile.id })) ?? []; // We can now use our seed client to insert tweets that will be linked to the profiles await seed.tweets(x => x(10), {connect: { profiles }}) -console.log('Profiles created: ', profiles) ``` - -Re-run the seed script with the environment variables set to your local Supabase instance: +We can now run the seed script with the environment variables set to your local Supabase instance: `NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_ANON_KEY= npx tsx seed.ts`: From 2ac535223fe6e2883aeafc0385c813ce93db9215 Mon Sep 17 00:00:00 2001 From: Justin van der Merwe Date: Thu, 16 May 2024 17:07:28 +0200 Subject: [PATCH 8/8] main-ify --- seed/supabase-twitter-clone/README.md | 135 +++++++++++++------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/seed/supabase-twitter-clone/README.md b/seed/supabase-twitter-clone/README.md index 1c58785..27040cf 100644 --- a/seed/supabase-twitter-clone/README.md +++ b/seed/supabase-twitter-clone/README.md @@ -246,33 +246,38 @@ First we need to change `seed.ts` to create some users using the Supabase SDK. import { createSeedClient } from '@snaplet/seed'; import { copycat } from '@snaplet/copycat'; import { createClient } from '@supabase/supabase-js'; +import { Database } from './lib/database.types' -const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - // Note you might want to use `SUPABASE_ROLE` key here with `auth.admin.signUp` if your app is using email confirmation - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! -); - -const PASSWORD = "testuser"; - -for (let i = 0; i < 5; i++) { - const email = copycat.email(i).toLowerCase(); - const avatar = faker.image.avatarGitHub(); - const fullName = copycat.fullName(i); - const userName = copycat.username(i); - - await supabase.auth.signUp({ - email, - password: PASSWORD, - options: { - data: { - avatar_url: avatar, - name: fullName, - user_name: userName, +const main = () => { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + // Note you might want to use `SUPABASE_ROLE` key here with `auth.admin.signUp` if your app is using email confirmation + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ); + + const PASSWORD = "testuser"; + + for (let i = 0; i < 5; i++) { + const email = copycat.email(i).toLowerCase(); + const avatar = faker.image.avatarGitHub(); + const fullName = copycat.fullName(i); + const userName = copycat.username(i); + + await supabase.auth.signUp({ + email, + password: PASSWORD, + options: { + data: { + avatar_url: avatar, + name: fullName, + user_name: userName, + } } - } - }); + }); + } } + +main() ``` This process creates a pool of 5 users with email and password logins, allowing us to easily log in as any tweet creator. It will also create the corresponding rows in the `profiles` table. @@ -300,59 +305,55 @@ Combining all the steps, our `seed.ts` file becomes: ```ts import { createSeedClient } from '@snaplet/seed'; -import { createClient } from '@supabase/supabase-js' -import { Database } from './lib/database.types' -import { copycat, faker } from '@snaplet/copycat' - +import { faker, copycat } from '@snaplet/copycat'; +import { createClient } from '@supabase/supabase-js'; +import { Database } from './lib/database.types'; -const seed = await createSeedClient({ - models: { - profiles: { - data: { - avatarUrl: ({ seed }) => faker.image.avatarGitHub(), - } - } - } -}); +const main = async () => { + const seed = await createSeedClient(); -const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, -) + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + // Note you might want to use `SUPABASE_ROLE` key here with `auth.admin.signUp` if your app is using email confirmation + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ); -// Clears all existing data in the database, but keep the structure -await seed.$resetDatabase() + const PASSWORD = 'testuser'; + for (let i = 0; i < 5; i++) { + const email = copycat.email(i).toLowerCase(); + const avatar = faker.image.avatarGitHub(); + const fullName = copycat.fullName(i); + const userName = copycat.username(i); -const PASSWORD = "testuser"; -for (let i = 0; i < 5; i += 1) { - const email = copycat.email(i).toLowerCase(); - const avatar: string = faker.image.avatarGitHub(); - const fullName: string = copycat.fullName(i); - const userName: string = copycat.username(i); - await supabase.auth.signUp({ + await supabase.auth.signUp({ email, password: PASSWORD, options: { - data: { - avatar_url: avatar, - name: fullName, - user_name: userName, - } - } - }); -} + data: { + avatar_url: avatar, + name: fullName, + user_name: userName, + }, + }, + }); + } -// * In our app, all our data under public isn't directly linked under the auth.user table but rather under the -// public.profiles table. -// * For any user inserted in the auth.users table we have a trigger that will insert a row in the public.profiles table -// * Since `supabase.auth.signUp()` created a user, we should now have all the profiles created as well -const { data: databaseProfiles } = await supabase.from("profiles").select() + const { data: databaseProfiles } = await supabase.from('profiles').select(); -// We convert our database fields to something that our seed client can understand -const profiles = databaseProfiles?.map(profile => ({ id: profile.id })) ?? []; + const profiles = + databaseProfiles?.map((profile) => ({ id: profile.id })) ?? []; -// We can now use our seed client to insert tweets that will be linked to the profiles -await seed.tweets(x => x(10), {connect: { profiles }}) + // Insert tweets linked to profiles + await seed.tweets((x) => x(10), { connect: { profiles } }); + + // Type completion not working? You might want to reload your TypeScript Server to pick up the changes + + console.log('Database seeded successfully!'); + + process.exit(); +}; + +main(); ```