-
Notifications
You must be signed in to change notification settings - Fork 18
Add: Dashboard db migrations & tooling #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
93c4623
add: dashboard specific db migrations & tooling
ben-fornefeld 7a7a125
add: auth_users postgres view migration
ben-fornefeld 161d57f
add: migration tracking, README.md content
ben-fornefeld 8610dfd
improve: auth_users migration
ben-fornefeld 60a7f6f
fix: url rewrite order in middleware
ben-fornefeld 16aa2a6
refactor: apply-migrations.ts to use default pg package and get rid o…
ben-fornefeld 4200eab
update: migrations/20250205180205.sql
ben-fornefeld 56c7008
chore: de-reference migration application but keep state for future i…
ben-fornefeld dd82db1
remove: apply-migrations.ts + chore: update README
ben-fornefeld aacdaf3
Merge branch 'main' into set-up-dashboard-db-migrations-e2b-1753
ben-fornefeld File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,21 +75,28 @@ vercel storage add | |
3. Copy the `anon key` and `service_role key` | ||
4. Copy the project URL | ||
|
||
#### c. Supabase Storage Setup | ||
#### c. Database Setup | ||
1. Retrieve the `POSTGRES_CONNECTION_STRING` from the Supabase project settings | ||
2. Run the migrations by running the following command: | ||
```bash | ||
bun run db:migrations:apply | ||
``` | ||
|
||
#### d. Supabase Storage Setup | ||
1. Go to Storage > Buckets | ||
2. Create a new **public** bucket named `profile-pictures` | ||
3. Apply storage access policies by running the SQL from [supabase/policies/buckets.sql](supabase/policies/buckets.sql) in the Supabase SQL Editor: | ||
3. Apply storage access policies by running the SQL from [migrations/supabase/profile-picture-bucket.sql](migrations/supabase/profile-picture-bucket.sql) in the Supabase SQL Editor: | ||
- These policies ensure only Supabase admin (service role) can write to and list files in the bucket | ||
- Public URLs are accessible for downloading files if the exact path is known | ||
- Regular users cannot browse, upload, update, or delete files in the bucket | ||
|
||
#### d. Environment Variables | ||
#### e. Environment Variables | ||
```bash | ||
# Copy the example env file | ||
cp .env.example .env.local | ||
``` | ||
|
||
#### e. Cookie Encryption | ||
#### f. Cookie Encryption | ||
The dashboard uses encrypted cookies for secure data storage. You'll need to set up a `COOKIE_ENCRYPTION_KEY`: | ||
|
||
```bash | ||
|
@@ -179,3 +186,92 @@ If you need help or have questions: | |
This project is licensed under the Apache License, Version 2.0 - see the [LICENSE](LICENSE) file for details. | ||
|
||
Copyright 2025 FoundryLabs, Inc. | ||
|
||
## Database Migrations | ||
|
||
The dashboard uses a custom migration system to manage database schema changes in a controlled, versioned manner. | ||
|
||
### Migration System Overview | ||
|
||
- **Timestamp-based**: Migrations are ordered by timestamp prefixes (YYYYMMDDHHMMSS) | ||
- **Idempotent**: Each migration is applied only once | ||
- **Transactional**: Migrations run in transactions for atomic changes | ||
- **Integrity checks**: Checksums verify migrations haven't been modified after application | ||
- **SQL-native**: Write pure SQL migrations with full PostgreSQL feature support | ||
|
||
### Creating Migrations | ||
|
||
To create a new migration: | ||
|
||
```bash | ||
# Create a migration with a description | ||
bun run db:migrations:create "add user profiles" | ||
# Creates: migrations/20250315123045.sql with description comment | ||
|
||
# Or create without description | ||
bun run db:migrations:create | ||
# Creates: migrations/20250315123045.sql | ||
``` | ||
|
||
### Writing Migrations | ||
|
||
Migrations are SQL files that can contain multiple statements, including complex PostgreSQL features: | ||
|
||
```sql | ||
-- Migration: add user profiles | ||
-- Timestamp: 20250315123045 | ||
|
||
-- Create profiles table | ||
CREATE TABLE profiles ( | ||
id UUID PRIMARY KEY REFERENCES auth.users(id), | ||
display_name TEXT, | ||
bio TEXT, | ||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP | ||
); | ||
|
||
-- Create function to handle profile creation | ||
CREATE OR REPLACE FUNCTION create_profile_for_new_user() | ||
RETURNS TRIGGER AS $$ | ||
BEGIN | ||
INSERT INTO profiles (id) | ||
VALUES (NEW.id); | ||
RETURN NEW; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
-- Set up trigger | ||
CREATE TRIGGER on_user_created | ||
AFTER INSERT ON auth.users | ||
FOR EACH ROW | ||
EXECUTE FUNCTION create_profile_for_new_user(); | ||
``` | ||
|
||
### Applying Migrations | ||
|
||
To apply pending migrations: | ||
|
||
```bash | ||
bun run db:migrations:apply | ||
``` | ||
|
||
This will: | ||
1. Create the migrations tracking table if it doesn't exist | ||
2. Apply any migrations that haven't been run yet | ||
3. Skip already-applied migrations | ||
4. Provide a summary of applied/skipped migrations | ||
|
||
### Migration Safety | ||
|
||
The system includes several safety features: | ||
- Migrations are applied in a transaction (all-or-nothing) | ||
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. not true! |
||
- Each migration is recorded with a checksum to detect modifications | ||
- Warnings are shown if a previously applied migration file has changed | ||
- Execution stops on the first error to prevent partial migrations | ||
|
||
### Best Practices | ||
|
||
- **One change per migration**: Keep migrations focused on a single logical change | ||
- **Backward compatibility**: When possible, design migrations to be backward compatible | ||
- **Comments**: Document the purpose of complex migrations | ||
- **Testing**: Test migrations in development before applying to production | ||
- **Avoid modifying applied migrations**: Create a new migration instead of changing an existing one |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
This migration adds team slugs and profile pictures to support user-friendly URLs and team branding. | ||
|
||
It performs the following steps: | ||
|
||
1. Adds two new columns to the teams table: | ||
- slug: A URL-friendly version of the team name (e.g. "acme-inc") | ||
- profile_picture_url: URL to the team's profile picture | ||
|
||
2. Creates a slug generation function that: | ||
- Takes a team name and converts it to a URL-friendly format | ||
- Removes special characters, accents, and spaces | ||
- Handles email addresses by only using the part before @ | ||
- Converts to lowercase and replaces spaces with hyphens | ||
|
||
3. Installs the unaccent PostgreSQL extension for proper accent handling | ||
|
||
4. Generates initial slugs for all existing teams: | ||
- Uses the team name as base for the slug | ||
- If multiple teams would have the same slug, appends part of the team ID | ||
to ensure uniqueness | ||
|
||
5. Sets up automatic slug generation for new teams: | ||
- Creates a trigger that runs before team insertion | ||
- Generates a unique slug using random suffixes if needed | ||
- Only generates a slug if one isn't explicitly provided | ||
|
||
6. Enforces slug uniqueness with a database constraint | ||
*/ | ||
|
||
ALTER TABLE teams | ||
ADD COLUMN slug TEXT, | ||
ADD COLUMN profile_picture_url TEXT; | ||
|
||
CREATE OR REPLACE FUNCTION generate_team_slug(name TEXT) | ||
RETURNS TEXT AS $$ | ||
DECLARE | ||
base_name TEXT; | ||
BEGIN | ||
base_name := SPLIT_PART(name, '@', 1); | ||
|
||
RETURN LOWER( | ||
REGEXP_REPLACE( | ||
REGEXP_REPLACE( | ||
UNACCENT(TRIM(base_name)), | ||
'[^a-zA-Z0-9\s-]', | ||
'', | ||
'g' | ||
), | ||
'\s+', | ||
'-', | ||
'g' | ||
) | ||
); | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
CREATE EXTENSION IF NOT EXISTS unaccent; | ||
|
||
WITH numbered_teams AS ( | ||
SELECT | ||
id, | ||
name, | ||
generate_team_slug(name) as base_slug, | ||
ROW_NUMBER() OVER (PARTITION BY generate_team_slug(name) ORDER BY created_at) as slug_count | ||
FROM teams | ||
WHERE slug IS NULL | ||
) | ||
UPDATE teams | ||
SET slug = | ||
CASE | ||
WHEN t.slug_count = 1 THEN t.base_slug | ||
ELSE t.base_slug || '-' || SUBSTRING(teams.id::text, 1, 4) | ||
END | ||
FROM numbered_teams t | ||
WHERE teams.id = t.id; | ||
|
||
CREATE OR REPLACE FUNCTION generate_team_slug_trigger() | ||
RETURNS TRIGGER AS $$ | ||
DECLARE | ||
base_slug TEXT; | ||
test_slug TEXT; | ||
suffix TEXT; | ||
BEGIN | ||
IF NEW.slug IS NULL THEN | ||
base_slug := generate_team_slug(NEW.name); | ||
test_slug := base_slug; | ||
|
||
WHILE EXISTS (SELECT 1 FROM teams WHERE slug = test_slug) LOOP | ||
suffix := SUBSTRING(gen_random_uuid()::text, 33, 4); | ||
ben-fornefeld marked this conversation as resolved.
Show resolved
Hide resolved
|
||
test_slug := base_slug || '-' || suffix; | ||
END LOOP; | ||
|
||
NEW.slug := test_slug; | ||
END IF; | ||
RETURN NEW; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
CREATE TRIGGER team_slug_trigger | ||
BEFORE INSERT ON teams | ||
FOR EACH ROW | ||
EXECUTE FUNCTION generate_team_slug_trigger(); | ||
|
||
ALTER TABLE teams | ||
ADD CONSTRAINT teams_slug_unique UNIQUE (slug); | ||
|
||
ALTER TABLE teams | ||
ALTER COLUMN slug SET NOT NULL; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
This migration creates a public view 'auth_users' that exposes selected columns | ||
from the auth.users table, making it easier to query user data from the public schema. | ||
*/ | ||
|
||
CREATE OR REPLACE VIEW public.auth_users AS | ||
jakubno marked this conversation as resolved.
Show resolved
Hide resolved
|
||
SELECT | ||
* | ||
FROM auth.users; | ||
|
||
-- Grant SELECT permissions to supabase_admin role | ||
GRANT SELECT ON public.auth_users TO supabase_admin; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.