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:

  1. Start the app using either:

    • yarn ios-simulator for iOS
    • yarn android for Android
  2. Log into the app and verify the following:

    • Navigate to the Profile screen and test that you can:

      • Upload a profile picture
      • Set your username
      • Add a website URL
      • Write a bio
    • Go to the Feedback screen and verify you can:

      • Submit a feature request
      • Report a bug
  3. 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.