Launchtoday comes with pre-built profile and feedback screens that require specific database tables and security policies to function. This guide provides the necessary SQL setup that powers these features:
- The profiles table that drives the Profile screen, including user avatars and profile information
- The feedback table that enables the Feedback submission screen
- An avatars storage bucket for profile images
- Security policies to protect user data
Quick Setup
For a quick setup, you can run the complete SQL script below in your Supabase SQL editor (the URL should look like this: https://supabase.com/project/*your-project-id*/sql/new
):
-- Create a table for public profiles
create table profiles (
id uuid references auth.users on delete cascade not null primary key,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
bio text,
has_seen_onboarding boolean,
full_name text,
constraint username_length check (char_length(username) >= 3)
);
-- Set up Row Level Security (RLS)
alter table profiles
enable row level security;
create policy "Public profiles are viewable by everyone." on profiles
for select using (true);
create policy "Users can insert their own profile." on profiles
for insert with check ((select auth.uid()) = id);
create policy "Users can update own profile." on profiles
for update using ((select auth.uid()) = id);
-- Create feedback table
create table feedback (
id bigint primary key,
user_id uuid references auth.users not null,
type text,
title text,
description text,
created_at timestamp with time zone
);
-- Set up Row Level Security (RLS) for feedback table
alter table feedback
enable row level security;
-- Add policies for feedback table
create policy "Users can view their own feedback." on feedback
for select using ((select auth.uid()) = user_id);
create policy "Users can insert their own feedback." on feedback
for insert with check ((select auth.uid()) = user_id);
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
create function public.handle_new_user()
returns trigger
set search_path = ''
as $$
begin
insert into public.profiles (id, full_name, avatar_url)
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
-- Set up Storage!
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
-- Set up storage policies
CREATE POLICY "allow_avatar_reads"
ON storage.objects FOR SELECT
USING (
bucket_id = 'avatars'::text
AND auth.role() = 'authenticated'::text
);
CREATE POLICY "allow_avatar_uploads"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'avatars'::text
AND auth.role() = 'authenticated'::text
AND (auth.uid())::text = (storage.foldername(name))[1]
);
CREATE POLICY "allow_avatar_updates"
ON storage.objects FOR UPDATE
USING (
bucket_id = 'avatars'::text
AND auth.role() = 'authenticated'::text
AND (auth.uid())::text = (storage.foldername(name))[1]
);
CREATE POLICY "allow_avatar_deletes"
ON storage.objects FOR DELETE
USING (
bucket_id = 'avatars'::text
AND auth.role() = 'authenticated'::text
AND (auth.uid())::text = (storage.foldername(name))[1]
);
Step-by-Step Setup Guide
1. Profiles Table
First, let’s set up the profiles table that stores user information:
-- Create profiles table
create table profiles (
id uuid references auth.users on delete cascade not null primary key,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
bio text,
has_seen_onboarding boolean,
full_name text,
constraint username_length check (char_length(username) >= 3)
);
-- Enable RLS and set up profile policies
alter table profiles enable row level security;
create policy "Public profiles are viewable by everyone."
on profiles for select using (true);
create policy "Users can insert their own profile."
on profiles for insert with check ((select auth.uid()) = id);
create policy "Users can update own profile."
on profiles for update using ((select auth.uid()) = id);
What this does:
- Creates a profiles table linked to auth.users
- Enables row-level security
- Sets up policies for viewing, creating, and updating profiles
2. Automatic Profile Creation
Set up automatic profile creation when users sign up:
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, full_name, avatar_url)
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
What this does:
- Creates a trigger function that runs when new users sign up
- Automatically creates profile entries for new users
3. Feedback Table
Next, set up the feedback system:
create table feedback (
id bigint primary key,
user_id uuid references auth.users not null,
type text,
title text,
description text,
created_at timestamp with time zone
);
-- Enable RLS and set up feedback policies
alter table feedback enable row level security;
create policy "Users can view their own feedback."
on feedback for select using ((select auth.uid()) = user_id);
create policy "Users can insert their own feedback."
on feedback for insert with check ((select auth.uid()) = user_id);
What this does:
- Creates a feedback table for user submissions
- Enables row-level security
- Sets up policies so users can only view and create their own feedback
4. Avatar Storage
Finally, set up the storage system for profile pictures:
-- Create avatar storage bucket
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
-- Set up storage policies
CREATE POLICY "allow_avatar_reads"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars'::text AND auth.role() = 'authenticated'::text);
CREATE POLICY "allow_avatar_uploads"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'avatars'::text
AND auth.role() = 'authenticated'::text
AND (auth.uid())::text = (storage.foldername(name))[1]
);
-- Similar policies for updates and deletes...
What this does:
- Creates a storage bucket for avatars
- Sets up policies for reading, uploading, updating, and deleting avatars
Verifying the Setup
After running the scripts, verify your setup by testing the app:
-
Start the app using either:
yarn ios-simulator
for iOS
yarn android
for Android
-
Log into the app and verify the following:
-
Check your Supabase dashboard to confirm:
- New entries appear in the profiles table
- Feedback submissions show up in the feedback table
- Profile images are stored in the avatars storage bucket
If you encounter any issues, check the app logs and Supabase dashboard for error messages.