-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSETUP_DB.sql
More file actions
207 lines (176 loc) · 7.31 KB
/
Copy pathSETUP_DB.sql
File metadata and controls
207 lines (176 loc) · 7.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
-- 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 default timezone('utc'::text, now()),
username text unique,
full_name text,
avatar_url text,
bio text, -- Renamed from biography (About Me)
profession text,
is_available boolean default true, -- Current Status
status_message text,
github_username text, -- For Contribution Graph
github_graph_title text default '', -- Added custom title for GitHub Graph
status_icon text default null, -- Status Emoji
social_links jsonb default '[]'::jsonb, -- Store links: [{ name, icon, href }]
tech_stack jsonb default '[]'::jsonb, -- Store list of tech: [{ name, icon }]
about_me text, -- Separate field for the About Me card
cta_title text,
cta_description text,
cta_text text,
cta_link text,
theme text default 'dark',
layout text default 'classic', -- Public profile layout: 'classic' | 'minimal'
loader_delay_ms integer default 0, -- Minimal-layout boot screen duration in ms; 0 = off, opt in from the dashboard
profile_font text default 'editorial', -- Public profile typography preset; see lib/profileFonts.ts
beams_enabled boolean default true,
is_donor boolean default false,
cv_url text,
constraint username_length check (char_length(username) >= 3),
-- 0 = off, otherwise keep it inside a sane range so a profile can't be
-- bricked behind a 10-minute loading screen.
constraint loader_delay_range check (loader_delay_ms >= 0 and loader_delay_ms <= 8000)
);
-- Create a table for Projects
create table projects (
id uuid default gen_random_uuid() primary key,
user_id uuid references profiles(id) on delete cascade not null,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
title text not null,
description text,
url text,
image_url text, -- Project Logo URL
tech_tags jsonb default '[]'::jsonb, -- Array of tech strings e.g. ["Next.js", "React"]
sort_order integer default 0, -- For rearranging projects
is_hidden boolean default false not null -- Hidden projects are excluded from the public portfolio
);
-- Table for tracking profile views
create table profile_views (
id uuid default gen_random_uuid() primary key,
profile_id uuid references profiles(id) on delete cascade not null,
viewed_at timestamp with time zone default timezone('utc'::text, now()) not null,
viewer_country text,
viewer_country_code text
);
-- Table for tracking link clicks
create table link_clicks (
id uuid default gen_random_uuid() primary key,
profile_id uuid references profiles(id) on delete cascade not null,
link_type text not null, -- 'social', 'project', 'cta'
link_url text not null,
clicked_at timestamp with time zone default timezone('utc'::text, now()) not null,
viewer_country text,
viewer_country_code text
);
-- Set up Row Level Security (RLS) for Profiles
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 );
-- Set up RLS for Projects
alter table projects enable row level security;
create policy "Public projects are viewable by everyone."
on projects for select
using ( true );
create policy "Users can insert their own projects."
on projects for insert
with check ( auth.uid() = user_id );
create policy "Users can update own projects."
on projects for update
using ( auth.uid() = user_id );
create policy "Users can delete own projects."
on projects for delete
using ( auth.uid() = user_id );
-- RLS for Analytics
alter table profile_views enable row level security;
alter table link_clicks enable row level security;
create policy "Public can record views."
on profile_views for insert
with check ( true );
create policy "Users can view own profile analytics."
on profile_views for select
using ( auth.uid() = profile_id );
create policy "Public can record clicks."
on link_clicks for insert
with check ( true );
create policy "Users can view own click analytics."
on link_clicks for select
using ( auth.uid() = profile_id );
-- Ensure username is unique
create unique index if not exists profiles_username_idx on profiles (username);
-- Function to handle new user signup automatically
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, full_name, avatar_url, username)
values (
new.id,
new.raw_user_meta_data->>'full_name',
new.raw_user_meta_data->>'avatar_url',
new.raw_user_meta_data->>'username'
);
return new;
end;
$$ language plpgsql security definer;
-- Trigger the function every time a user is created
create or replace trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
-- Create Storage bucket for Avatars and Project Images
insert into storage.buckets (id, name, public)
values ('images', 'images', true)
on conflict (id) do nothing;
-- Policy to allow public access to images
create policy "Images are publicly accessible."
on storage.objects for select
using ( bucket_id = 'images' );
-- Policy to allow authenticated users to upload images
create policy "Authenticated users can upload images."
on storage.objects for insert
with check ( bucket_id = 'images' and auth.role() = 'authenticated' );
-- Create Storage bucket for CVs
insert into storage.buckets (id, name, public)
values ('cvs', 'cvs', true)
on conflict (id) do nothing;
-- Policy to allow public access to CVs
create policy "CVs are publicly accessible."
on storage.objects for select
using ( bucket_id = 'cvs' );
-- Policy to allow authenticated users to upload CVs
create policy "Authenticated users can upload CVs."
on storage.objects for insert
with check ( bucket_id = 'cvs' and auth.role() = 'authenticated' );
-- ---------------------------------------------------------------------------
-- Migration: loader_delay_ms (minimal-layout boot screen duration)
-- Safe to run on an existing database; both statements are idempotent.
-- ---------------------------------------------------------------------------
alter table profiles
add column if not exists loader_delay_ms integer default 0;
-- Corrects the default on databases where an earlier revision of this migration
-- created the column with 2800. Does not touch existing rows — see the note in
-- the PR for the one-time backfill, which must not live here or it would keep
-- resetting anyone who deliberately picks 2.8s.
alter table profiles
alter column loader_delay_ms set default 0;
do $$
begin
if not exists (
select 1 from pg_constraint where conname = 'loader_delay_range'
) then
alter table profiles
add constraint loader_delay_range
check (loader_delay_ms >= 0 and loader_delay_ms <= 8000);
end if;
end $$;
-- ---------------------------------------------------------------------------
-- Migration: profile_font (public profile typography preset)
-- Safe to run on an existing database.
-- ---------------------------------------------------------------------------
alter table profiles
add column if not exists profile_font text default 'editorial';