mirror of
https://github.com/supabase/supabase.git
synced 2026-07-04 06:44:22 +08:00
* feat: create SUPABASE_DB_HOST, SUPABASE_DB_PASSWORD envs for new empty database Vercel integration * fix: prepareVercelEvns to handle missing project props * chore: tidy up
522 lines
14 KiB
TypeScript
522 lines
14 KiB
TypeScript
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants'
|
|
|
|
const QuickStart = SQL_TEMPLATES.filter((template) => template.type === 'quickstart')
|
|
|
|
export const INTEGRATION_ENVS_ALIAS = {
|
|
ENDPOINT: 'SUPABASE_ENDPOINT',
|
|
ANONKEY: 'SUPABASE_ANON_KEY',
|
|
SERVICEKEY: 'SUPABASE_SERVICE_KEY',
|
|
DBHOST: 'SUPABASE_DB_HOST',
|
|
DBPASSWORD: 'SUPABASE_DB_PASSWORD',
|
|
}
|
|
export const VERCEL_DEFAULT_EXTERNAL_ID = 'nextjs-initial-project'
|
|
/*
|
|
These values have to match our integrations setup (per project)
|
|
under https://vercel.com/dashboard/supabase/integrations/console
|
|
|
|
'id' needs to be the same as deploy button External ID
|
|
https://vercel.com/docs/more/deploy-button#external-id
|
|
*/
|
|
export const VERCEL_INTEGRATION_CONFIGS = [
|
|
{
|
|
id: 'supabase_realtime_map',
|
|
name: 'Realtime Map Database',
|
|
template: {
|
|
sql: `CREATE TABLE locations (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
latitude numeric NOT NULL,
|
|
longitude numeric NOT NULL,
|
|
ref VARCHAR(40)
|
|
);`,
|
|
},
|
|
envs: [
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_ENDPOINT',
|
|
alias: INTEGRATION_ENVS_ALIAS.ENDPOINT,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_APIKEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.ANONKEY,
|
|
type: 'encrypted',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'nextjs-subscription-payments',
|
|
name: 'Next.js Subscription Payments Starter',
|
|
template: QuickStart.find((x) => x.title == 'Stripe Subscriptions'),
|
|
envs: [
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_URL',
|
|
alias: INTEGRATION_ENVS_ALIAS.ENDPOINT,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.ANONKEY,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'SUPABASE_SERVICE_ROLE_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.SERVICEKEY,
|
|
type: 'encrypted',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'supabase-todo-list',
|
|
name: 'Todo List Database',
|
|
template: QuickStart.find((x) => x.title == 'Todo List'),
|
|
envs: [
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_URL',
|
|
alias: INTEGRATION_ENVS_ALIAS.ENDPOINT,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.ANONKEY,
|
|
type: 'encrypted',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'nextjs-initial-project',
|
|
name: 'New Empty Database',
|
|
template: {
|
|
sql: '',
|
|
},
|
|
envs: [
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_URL',
|
|
alias: INTEGRATION_ENVS_ALIAS.ENDPOINT,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.ANONKEY,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'SUPABASE_SERVICE_ROLE_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.SERVICEKEY,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'SUPABASE_DB_HOST',
|
|
alias: INTEGRATION_ENVS_ALIAS.DBHOST,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'SUPABASE_DB_PASSWORD',
|
|
alias: INTEGRATION_ENVS_ALIAS.DBPASSWORD,
|
|
type: 'encrypted',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'nextjs-user-management',
|
|
name: 'User Management Starter',
|
|
template: QuickStart.find((x) => x.title == 'User Management Starter'),
|
|
envs: [
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_URL',
|
|
alias: INTEGRATION_ENVS_ALIAS.ENDPOINT,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.ANONKEY,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'SUPABASE_SERVICE_ROLE_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.SERVICEKEY,
|
|
type: 'encrypted',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'threaded-comments',
|
|
name: 'Threaded Comments',
|
|
template: {
|
|
sql: `
|
|
-- Create a table for Public Profiles
|
|
create table profiles (
|
|
id uuid references auth.users not null,
|
|
updated_at timestamp with time zone,
|
|
username text unique,
|
|
full_name text,
|
|
avatar_url text,
|
|
website text,
|
|
|
|
primary key (id),
|
|
unique (username),
|
|
constraint username_length check (char_length(username) >= 3)
|
|
);
|
|
|
|
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 ( auth.uid() = id );
|
|
|
|
create policy "Users can update own profile."
|
|
on profiles for update
|
|
using ( auth.uid() = id );
|
|
|
|
-- Create a trigger to sync profiles and auth.users
|
|
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();
|
|
|
|
|
|
-- Create a table for posts
|
|
create table posts (
|
|
id bigserial not null,
|
|
slug text not null unique,
|
|
"createdAt" timestamp with time zone default now() not null,
|
|
"updatedAt" timestamp with time zone default now() null,
|
|
title text null,
|
|
content text null,
|
|
"isPublished" boolean default false not null,
|
|
"authorId" uuid not null references profiles (id),
|
|
"parentId" bigint null references posts (id),
|
|
live boolean default true null,
|
|
"siteId" bigint default '1' ::bigint not null,
|
|
"isPinned" boolean default false not null,
|
|
"isDeleted" boolean default false not null,
|
|
"isApproved" boolean default false not null,
|
|
|
|
primary key (id),
|
|
unique (slug)
|
|
);
|
|
|
|
alter table posts enable row level security;
|
|
|
|
-- Create root user and initial post. Triggers update in profiles.
|
|
insert into auth.users (id) values ('00000000-0000-0000-0000-000000000000'::uuid);
|
|
|
|
update profiles
|
|
set
|
|
full_name = 'Admin',
|
|
avatar_url = 'https://assets3.thrillist.com/v1/image/1875552/414x310/crop;jpeg_quality=65.jpg'
|
|
where
|
|
id = '00000000-0000-0000-0000-000000000000'::uuid;
|
|
|
|
insert into posts (slug, title, content, "authorId") values ('root', 'Root post', 'root post', '00000000-0000-0000-0000-000000000000'::uuid);
|
|
insert into posts (slug, title, content, "authorId", "parentId") values ('threaded-comments-123', 'threaded-comments', 'Threaded comments, built on top of Supabase and Next.js. Visit GitHub to deploy your own: https://github.com/lawrencecchen/threaded-comments', '00000000-0000-0000-0000-000000000000'::uuid, 1);
|
|
|
|
create policy "Posts are viewable by everyone."
|
|
on posts for select
|
|
using ( true );
|
|
|
|
create policy "Users can post as themselves."
|
|
on posts for insert
|
|
with check ( auth.uid() = "authorId" );
|
|
|
|
-- Create a table for sites
|
|
create table sites (
|
|
id bigserial not null,
|
|
"siteDomain" text not null,
|
|
"ownerId" uuid not null,
|
|
"name" text not null,
|
|
|
|
primary key (id)
|
|
);
|
|
|
|
alter table sites enable row level security;
|
|
|
|
create policy "Sites are viewable by everyone."
|
|
on sites for select
|
|
using ( true );
|
|
|
|
create policy "Users can create their own sites."
|
|
on sites for insert
|
|
with check ( auth.uid() = "ownerId" );
|
|
|
|
-- Create a table for votes
|
|
create table votes (
|
|
"postId" bigint not null references posts (id),
|
|
"userId" uuid not null references profiles (id),
|
|
"value" int not null,
|
|
|
|
primary key ("postId", "userId"),
|
|
constraint vote_quantity check (value <= 1 and value >= -1)
|
|
);
|
|
|
|
alter table votes enable row level security;
|
|
|
|
create policy "Votes are viewable by everyone"
|
|
on votes for select
|
|
using ( true );
|
|
|
|
create policy "Users can vote as themselves"
|
|
on votes for insert
|
|
with check (auth.uid() = "userId");
|
|
|
|
create policy "Users can update their own votes"
|
|
on votes for update
|
|
using ( auth.uid() = "userId" );
|
|
|
|
|
|
-- Set up Realtime!
|
|
begin;
|
|
drop publication if exists supabase_realtime;
|
|
create publication supabase_realtime;
|
|
commit;
|
|
alter publication supabase_realtime add table posts, sites, votes, profiles;
|
|
|
|
-- Set up Storage!
|
|
insert into storage.buckets (id, name)
|
|
values ('avatars', 'avatars');
|
|
|
|
create policy "Avatar images are publicly accessible."
|
|
on storage.objects for select
|
|
using ( bucket_id = 'avatars' );
|
|
|
|
create policy "Anyone can upload an avatar."
|
|
on storage.objects for insert
|
|
with check ( bucket_id = 'avatars' );
|
|
|
|
drop view if exists comments_thread_with_user_vote;
|
|
drop view if exists comments_thread;
|
|
drop view if exists comments_with_author_votes;
|
|
drop view if exists comments_linear_view;
|
|
drop view if exists comment_with_author;
|
|
|
|
create view comment_with_author as
|
|
select
|
|
p.id,
|
|
p.slug,
|
|
p."createdAt",
|
|
p."updatedAt",
|
|
p.title,
|
|
p.content,
|
|
p."isPublished",
|
|
p."authorId",
|
|
p."parentId",
|
|
p.live,
|
|
p."siteId",
|
|
p."isPinned",
|
|
p."isDeleted",
|
|
p."isApproved",
|
|
to_jsonb(u) as author
|
|
from
|
|
posts p
|
|
inner join profiles u on p."authorId" = u.id;
|
|
|
|
create view comments_linear_view as
|
|
select
|
|
root_c.*,
|
|
to_jsonb(parent_c) as parent,
|
|
coalesce(json_agg(children_c) filter (where children_c.id is not null), '[]') as responses
|
|
from
|
|
comment_with_author root_c
|
|
inner join comment_with_author parent_c on root_c."parentId" = parent_c.id
|
|
inner join sites s1 on s1.id = root_c."siteId"
|
|
left join comment_with_author children_c on children_c."parentId" = root_c.id
|
|
group by
|
|
root_c.id,
|
|
root_c.slug,
|
|
root_c."createdAt",
|
|
root_c."updatedAt",
|
|
root_c.title,
|
|
root_c.content,
|
|
root_c."isPublished",
|
|
root_c."authorId",
|
|
root_c."parentId",
|
|
root_c.live,
|
|
root_c."siteId",
|
|
root_c."isPinned",
|
|
root_c."isDeleted",
|
|
root_c."isApproved",
|
|
root_c.author,
|
|
parent_c.*;
|
|
|
|
create or replace view comments_with_author_votes as
|
|
select
|
|
p.id,
|
|
p.slug,
|
|
p."createdAt",
|
|
p."updatedAt",
|
|
p.title,
|
|
p.content,
|
|
p."isPublished",
|
|
p."authorId",
|
|
p."parentId",
|
|
p.live,
|
|
p."siteId",
|
|
p."isPinned",
|
|
p."isDeleted",
|
|
p."isApproved",
|
|
p."author",
|
|
coalesce (
|
|
sum (v.value) over w,
|
|
0
|
|
) as "votes",
|
|
sum (case when v.value > 0 then 1 else 0 end) over w as "upvotes",
|
|
sum (case when v.value < 0 then 1 else 0 end) over w as "downvotes"
|
|
-- (select case when auth.uid() = v."userId" then v.value else 0 end) as "userVoteValue"
|
|
from
|
|
comment_with_author p
|
|
left join votes v on p.id = v."postId"
|
|
window w as (
|
|
partition by v."postId"
|
|
);
|
|
|
|
create recursive view comments_thread (
|
|
id,
|
|
slug,
|
|
"createdAt",
|
|
"updatedAt",
|
|
title,
|
|
content,
|
|
"isPublished",
|
|
"authorId",
|
|
"parentId",
|
|
live,
|
|
"siteId",
|
|
"isPinned",
|
|
"isDeleted",
|
|
"isApproved",
|
|
"author",
|
|
"votes",
|
|
"upvotes",
|
|
"downvotes",
|
|
"depth",
|
|
"path",
|
|
"pathVotesRecent",
|
|
"pathLeastRecent",
|
|
"pathMostRecent"
|
|
) as
|
|
select
|
|
id,
|
|
slug,
|
|
"createdAt",
|
|
"updatedAt",
|
|
title,
|
|
content,
|
|
"isPublished",
|
|
"authorId",
|
|
"parentId",
|
|
live,
|
|
"siteId",
|
|
"isPinned",
|
|
"isDeleted",
|
|
"isApproved",
|
|
"author",
|
|
"votes",
|
|
"upvotes",
|
|
"downvotes",
|
|
0 as depth,
|
|
array[id] as "path",
|
|
array[id] as "pathVotesRecent",
|
|
array[id] as "pathLeastRecent",
|
|
array[id] as "pathMostRecent"
|
|
from
|
|
comments_with_author_votes
|
|
where
|
|
"parentId" is null
|
|
union all
|
|
select
|
|
p1.id,
|
|
p1.slug,
|
|
p1."createdAt",
|
|
p1."updatedAt",
|
|
p1.title,
|
|
p1.content,
|
|
p1."isPublished",
|
|
p1."authorId",
|
|
p1."parentId",
|
|
p1.live,
|
|
p1."siteId",
|
|
p1."isPinned",
|
|
p1."isDeleted",
|
|
p1."isApproved",
|
|
p1."author",
|
|
p1."votes",
|
|
p1."upvotes",
|
|
p1."downvotes",
|
|
p2.depth + 1 as depth,
|
|
p2."path" || p1.id::bigint as "path",
|
|
p2."pathVotesRecent" || -p1."votes"::bigint || -extract(epoch from p1."createdAt")::bigint || p1.id as "pathVotesRecent",
|
|
p2."pathLeastRecent" || extract(epoch from p1."createdAt")::bigint || p1.id as "pathLeastRecent",
|
|
p2."pathMostRecent" || -extract(epoch from p1."createdAt")::bigint || p1.id as "pathMostRecent"
|
|
from
|
|
comments_with_author_votes p1
|
|
join comments_thread p2 on p1."parentId" = p2.id;
|
|
|
|
create or replace view comments_thread_with_user_vote as
|
|
select distinct on (id)
|
|
id,
|
|
slug,
|
|
"createdAt",
|
|
"updatedAt",
|
|
title,
|
|
content,
|
|
"isPublished",
|
|
"authorId",
|
|
"parentId",
|
|
live,
|
|
"siteId",
|
|
"isPinned",
|
|
"isDeleted",
|
|
"isApproved",
|
|
"author",
|
|
"votes",
|
|
"upvotes",
|
|
"downvotes",
|
|
"depth",
|
|
"path",
|
|
"pathVotesRecent",
|
|
"pathLeastRecent",
|
|
"pathMostRecent",
|
|
coalesce(
|
|
(
|
|
select
|
|
v."value"
|
|
from
|
|
votes v
|
|
where
|
|
auth.uid() = v."userId" and v."postId" = id
|
|
),
|
|
0
|
|
) as "userVoteValue"
|
|
from comments_thread;
|
|
`,
|
|
},
|
|
envs: [
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_URL',
|
|
alias: INTEGRATION_ENVS_ALIAS.ENDPOINT,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.ANONKEY,
|
|
type: 'encrypted',
|
|
},
|
|
{
|
|
key: 'SUPABASE_SERVICE_ROLE_KEY',
|
|
alias: INTEGRATION_ENVS_ALIAS.SERVICEKEY,
|
|
type: 'encrypted',
|
|
},
|
|
],
|
|
},
|
|
]
|