-
Notifications
You must be signed in to change notification settings - Fork 2
docs: Changes to readme after testing out supabase example #11
base: main
Are you sure you want to change the base?
Changes from all commits
3ff7b09
be90964
fcc9ff6
48348d7
8f8bc4f
607b5b7
80913da
2ac5352
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
@@ -60,7 +61,6 @@ First, let's set up a local development environment for the Supabase Twitter clo | |
|
||
```bash | ||
npx supabase login | ||
npx supabase init | ||
``` | ||
|
||
 | ||
|
@@ -71,23 +71,11 @@ 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 <your-twitter-clone-project-id> | ||
# Create a valid migrations folder for Supabase to pull the first migration. | ||
mkdir -p supabase/migrations | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, the dir already existed. I guess we could keep this around regardless (won't error or anything), but didn't seem needed anymore. |
||
|
||
# Pull the database schema from the remote project. | ||
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; | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These were already in the migration file that was checked into the repo, so I guessed we don't need these lines in the readme anymore: https://github.com/snaplet/examples/blob/main/seed/supabase-twitter-clone/supabase/migrations/20240312180754_remote_schema.sql#L169-L171 |
||
|
||
Next, we must synchronize our local development project with the remote one: | ||
|
||
```bash | ||
|
@@ -183,44 +171,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=<user-email>&password=<user-password>` | ||
|
||
However, we still need to create a new user with email and password. This is where Snaplet Seed will be utilized. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was already in the repo: So I guessed we don't need it in the readme still. Or did I misunderstand, and we want to still keep it around in the readme regardless? |
||
### Setup @snaplet/seed | ||
|
||
To set it up: | ||
|
@@ -285,159 +235,129 @@ 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() | ||
|
||
// Generate 10 tweets | ||
await seed.tweets(x => x(10)) | ||
``` | ||
### Writing the Seed Script | ||
|
||
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(), | ||
import { createSeedClient } from '@snaplet/seed'; | ||
import { copycat } from '@snaplet/copycat'; | ||
import { createClient } from '@supabase/supabase-js'; | ||
import { Database } from './lib/database.types' | ||
|
||
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, | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
|
||
await seed.$resetDatabase() | ||
} | ||
|
||
// Generate 10 tweets with valid avatar URLs | ||
await seed.tweets(x => x(10)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running this script failed for me. The db relies on a I tried create this metadata field, it worked but then the next error was that At this point though, I figured if the next thing we're doing is showing users a how to create the users+profiles using the supabase sdk, then using So I changed the readme accordingly. |
||
main() | ||
``` | ||
|
||
We can now re-run our script with `npx tsx seed.ts`. | ||
|
||
Refreshing our page should now display the seeded tweet data correctly. | ||
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. | ||
|
||
To easily log in as the creators of these tweets, we integrate the Supabase SDK into our seed script: | ||
Now that we have this profile data, we can create some tweets using `@snaplet/seed`, and connect them up to these profiles: | ||
|
||
```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 | ||
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 { data: databaseProfiles } = await supabase.from("profiles").select(); | ||
// * 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 profiles = 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 })) ?? []; | ||
|
||
// Insert tweets linked to profiles | ||
await seed.tweets(x => x(10), { connect: { profiles } }); | ||
console.log("Profiles created: ", profiles); | ||
// 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 }}) | ||
``` | ||
|
||
This process creates a pool of 5 users with email and password logins, allowing us to easily log in as any tweet creator. | ||
|
||
Combining all the steps, our `seed.ts` file becomes: | ||
|
||
<details> | ||
<summary>Click to show the full code</summary> | ||
|
||
```ts | ||
import { createSeedClient, type profilesScalars } from '@snaplet/seed'; | ||
import { createClient } from '@supabase/supabase-js' | ||
import {Database} from './lib/database.types' | ||
import { copycat, faker } from '@snaplet/copycat' | ||
|
||
|
||
const seed = await createSeedClient({ | ||
models: { | ||
profiles: { | ||
data: { | ||
avatarUrl: ({ seed }) => faker.image.avatarGitHub(), | ||
} | ||
} | ||
import { createSeedClient } from '@snaplet/seed'; | ||
import { faker, copycat } from '@snaplet/copycat'; | ||
import { createClient } from '@supabase/supabase-js'; | ||
import { Database } from './lib/database.types'; | ||
|
||
const main = async () => { | ||
const seed = await createSeedClient(); | ||
|
||
const supabase = createClient<Database>( | ||
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 supabase = createClient<Database>( | ||
process.env.NEXT_PUBLIC_SUPABASE_URL!, | ||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, | ||
) | ||
const { data: databaseProfiles } = await supabase.from('profiles').select(); | ||
|
||
const profiles = | ||
databaseProfiles?.map((profile) => ({ id: profile.id })) ?? []; | ||
|
||
// Insert tweets linked to profiles | ||
await seed.tweets((x) => x(10), { connect: { profiles } }); | ||
|
||
// Clears all existing data in the database, but keep the structure | ||
await seed.$resetDatabase() | ||
// Type completion not working? You might want to reload your TypeScript Server to pick up the changes | ||
|
||
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({ | ||
email, | ||
password: PASSWORD, | ||
options: { | ||
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 | ||
// 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 | ||
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, | ||
})) ?? [] | ||
console.log('Database seeded successfully!'); | ||
|
||
// 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) | ||
``` | ||
process.exit(); | ||
}; | ||
|
||
main(); | ||
``` | ||
</details> | ||
|
||
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=<anon-key> npx tsx seed.ts`: | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got this when I ran this:
I guess we don't need to
init
anymore since we already checked in the stuff that was generated during init.