feat: add core database schema and demo seed data for Freelancer OS MVP

This commit is contained in:
Poyraz Avsever
2026-06-01 11:51:43 +03:00
parent 53a225594a
commit c0d25a87e8
8 changed files with 698 additions and 0 deletions
+11
View File
@@ -17,5 +17,16 @@ Future schema changes should be added under:
supabase/migrations/
```
Optional local/demo data should be added under:
```text
supabase/seeds/
```
Do not overwrite already executed SQL without also creating a new ordered migration and documenting why.
## Current Ordered SQL
1. `schema.sql`
2. `migrations/0002_add_freelancer_os_core_tables.sql`
3. Optional: `seeds/0001_demo_freelancer_os_data.sql`
@@ -0,0 +1,336 @@
-- 0002: Add Freelancer OS core tables
-- Run after: supabase/schema.sql
create extension if not exists "uuid-ossp";
create or replace function public.set_updated_at()
returns trigger as $$
begin
new.updated_at = timezone('utc'::text, now());
return new;
end;
$$ language plpgsql;
create table if not exists public.clients (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
name text not null,
company_name text,
email text,
phone text,
website text,
status text default 'active'::text not null,
notes text,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);
create table if not exists public.projects (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
client_id uuid references public.clients(id) on delete set null,
name text not null,
type text default 'client_project'::text not null,
description text,
status text default 'planning'::text not null,
start_date date,
due_date date,
budget_amount numeric(12, 2),
currency text default 'USD'::text not null,
progress integer default 0 not null,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);
create table if not exists public.calendar_events (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
client_id uuid references public.clients(id) on delete set null,
project_id uuid references public.projects(id) on delete set null,
task_id uuid references public.tasks(id) on delete set null,
title text not null,
description text,
type text default 'focus'::text not null,
starts_at timestamp with time zone not null,
ends_at timestamp with time zone,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);
create table if not exists public.finance_transactions (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
client_id uuid references public.clients(id) on delete set null,
project_id uuid references public.projects(id) on delete set null,
type text not null,
amount numeric(12, 2) not null,
currency text default 'USD'::text not null,
transaction_date date default current_date not null,
category text,
payment_status text default 'planned'::text not null,
description text,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);
create table if not exists public.daily_logs (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
log_date date default current_date not null,
mood_score smallint not null,
energy_score smallint not null,
work_satisfaction_score smallint,
note text,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null,
unique (user_id, log_date)
);
create table if not exists public.app_settings (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade not null unique,
timezone text default 'UTC'::text not null,
currency text default 'USD'::text not null,
ai_provider text default 'ollama'::text not null,
ai_model text,
api_key text,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table public.tasks
add column if not exists client_id uuid references public.clients(id) on delete set null,
add column if not exists project_id uuid references public.projects(id) on delete set null,
add column if not exists priority text default 'medium'::text not null,
add column if not exists due_at timestamp with time zone,
add column if not exists estimated_minutes integer,
add column if not exists actual_minutes integer;
do $$
begin
if not exists (select 1 from pg_constraint where conname = 'clients_status_check') then
alter table public.clients
add constraint clients_status_check
check (status in ('active', 'paused', 'archived'));
end if;
if not exists (select 1 from pg_constraint where conname = 'projects_type_check') then
alter table public.projects
add constraint projects_type_check
check (type in ('client_project', 'side_project'));
end if;
if not exists (select 1 from pg_constraint where conname = 'projects_status_check') then
alter table public.projects
add constraint projects_status_check
check (status in ('planning', 'active', 'paused', 'completed', 'cancelled'));
end if;
if not exists (select 1 from pg_constraint where conname = 'projects_progress_check') then
alter table public.projects
add constraint projects_progress_check
check (progress >= 0 and progress <= 100);
end if;
if not exists (select 1 from pg_constraint where conname = 'tasks_priority_check') then
alter table public.tasks
add constraint tasks_priority_check
check (priority in ('low', 'medium', 'high', 'urgent'));
end if;
if not exists (select 1 from pg_constraint where conname = 'calendar_events_type_check') then
alter table public.calendar_events
add constraint calendar_events_type_check
check (type in ('meeting', 'focus', 'deadline', 'personal', 'finance'));
end if;
if not exists (select 1 from pg_constraint where conname = 'calendar_events_time_check') then
alter table public.calendar_events
add constraint calendar_events_time_check
check (ends_at is null or ends_at >= starts_at);
end if;
if not exists (select 1 from pg_constraint where conname = 'finance_transactions_type_check') then
alter table public.finance_transactions
add constraint finance_transactions_type_check
check (type in ('income', 'expense'));
end if;
if not exists (select 1 from pg_constraint where conname = 'finance_transactions_amount_check') then
alter table public.finance_transactions
add constraint finance_transactions_amount_check
check (amount >= 0);
end if;
if not exists (select 1 from pg_constraint where conname = 'finance_transactions_payment_status_check') then
alter table public.finance_transactions
add constraint finance_transactions_payment_status_check
check (payment_status in ('planned', 'pending', 'paid', 'cancelled'));
end if;
if not exists (select 1 from pg_constraint where conname = 'daily_logs_mood_score_check') then
alter table public.daily_logs
add constraint daily_logs_mood_score_check
check (mood_score between 1 and 5);
end if;
if not exists (select 1 from pg_constraint where conname = 'daily_logs_energy_score_check') then
alter table public.daily_logs
add constraint daily_logs_energy_score_check
check (energy_score between 1 and 5);
end if;
if not exists (select 1 from pg_constraint where conname = 'daily_logs_work_satisfaction_score_check') then
alter table public.daily_logs
add constraint daily_logs_work_satisfaction_score_check
check (work_satisfaction_score is null or work_satisfaction_score between 1 and 5);
end if;
if not exists (select 1 from pg_constraint where conname = 'app_settings_ai_provider_check') then
alter table public.app_settings
add constraint app_settings_ai_provider_check
check (ai_provider in ('ollama', 'openai', 'gemini', 'groq'));
end if;
end $$;
create index if not exists clients_user_id_idx on public.clients(user_id);
create index if not exists clients_status_idx on public.clients(status);
create index if not exists projects_user_id_idx on public.projects(user_id);
create index if not exists projects_client_id_idx on public.projects(client_id);
create index if not exists projects_status_idx on public.projects(status);
create index if not exists projects_due_date_idx on public.projects(due_date);
create index if not exists tasks_client_id_idx on public.tasks(client_id);
create index if not exists tasks_project_id_idx on public.tasks(project_id);
create index if not exists tasks_due_at_idx on public.tasks(due_at);
create index if not exists tasks_priority_idx on public.tasks(priority);
create index if not exists calendar_events_user_id_idx on public.calendar_events(user_id);
create index if not exists calendar_events_starts_at_idx on public.calendar_events(starts_at);
create index if not exists calendar_events_project_id_idx on public.calendar_events(project_id);
create index if not exists calendar_events_task_id_idx on public.calendar_events(task_id);
create index if not exists finance_transactions_user_id_idx on public.finance_transactions(user_id);
create index if not exists finance_transactions_client_id_idx on public.finance_transactions(client_id);
create index if not exists finance_transactions_project_id_idx on public.finance_transactions(project_id);
create index if not exists finance_transactions_transaction_date_idx on public.finance_transactions(transaction_date);
create index if not exists finance_transactions_payment_status_idx on public.finance_transactions(payment_status);
create index if not exists daily_logs_user_id_idx on public.daily_logs(user_id);
create index if not exists daily_logs_log_date_idx on public.daily_logs(log_date);
alter table public.clients enable row level security;
alter table public.projects enable row level security;
alter table public.calendar_events enable row level security;
alter table public.finance_transactions enable row level security;
alter table public.daily_logs enable row level security;
alter table public.app_settings enable row level security;
drop policy if exists "Users can view their own clients." on public.clients;
create policy "Users can view their own clients." on public.clients
for select using (auth.uid() = user_id);
drop policy if exists "Users can insert their own clients." on public.clients;
create policy "Users can insert their own clients." on public.clients
for insert with check (auth.uid() = user_id);
drop policy if exists "Users can update their own clients." on public.clients;
create policy "Users can update their own clients." on public.clients
for update using (auth.uid() = user_id);
drop policy if exists "Users can delete their own clients." on public.clients;
create policy "Users can delete their own clients." on public.clients
for delete using (auth.uid() = user_id);
drop policy if exists "Users can view their own projects." on public.projects;
create policy "Users can view their own projects." on public.projects
for select using (auth.uid() = user_id);
drop policy if exists "Users can insert their own projects." on public.projects;
create policy "Users can insert their own projects." on public.projects
for insert with check (auth.uid() = user_id);
drop policy if exists "Users can update their own projects." on public.projects;
create policy "Users can update their own projects." on public.projects
for update using (auth.uid() = user_id);
drop policy if exists "Users can delete their own projects." on public.projects;
create policy "Users can delete their own projects." on public.projects
for delete using (auth.uid() = user_id);
drop policy if exists "Users can view their own calendar events." on public.calendar_events;
create policy "Users can view their own calendar events." on public.calendar_events
for select using (auth.uid() = user_id);
drop policy if exists "Users can insert their own calendar events." on public.calendar_events;
create policy "Users can insert their own calendar events." on public.calendar_events
for insert with check (auth.uid() = user_id);
drop policy if exists "Users can update their own calendar events." on public.calendar_events;
create policy "Users can update their own calendar events." on public.calendar_events
for update using (auth.uid() = user_id);
drop policy if exists "Users can delete their own calendar events." on public.calendar_events;
create policy "Users can delete their own calendar events." on public.calendar_events
for delete using (auth.uid() = user_id);
drop policy if exists "Users can view their own finance transactions." on public.finance_transactions;
create policy "Users can view their own finance transactions." on public.finance_transactions
for select using (auth.uid() = user_id);
drop policy if exists "Users can insert their own finance transactions." on public.finance_transactions;
create policy "Users can insert their own finance transactions." on public.finance_transactions
for insert with check (auth.uid() = user_id);
drop policy if exists "Users can update their own finance transactions." on public.finance_transactions;
create policy "Users can update their own finance transactions." on public.finance_transactions
for update using (auth.uid() = user_id);
drop policy if exists "Users can delete their own finance transactions." on public.finance_transactions;
create policy "Users can delete their own finance transactions." on public.finance_transactions
for delete using (auth.uid() = user_id);
drop policy if exists "Users can view their own daily logs." on public.daily_logs;
create policy "Users can view their own daily logs." on public.daily_logs
for select using (auth.uid() = user_id);
drop policy if exists "Users can insert their own daily logs." on public.daily_logs;
create policy "Users can insert their own daily logs." on public.daily_logs
for insert with check (auth.uid() = user_id);
drop policy if exists "Users can update their own daily logs." on public.daily_logs;
create policy "Users can update their own daily logs." on public.daily_logs
for update using (auth.uid() = user_id);
drop policy if exists "Users can delete their own daily logs." on public.daily_logs;
create policy "Users can delete their own daily logs." on public.daily_logs
for delete using (auth.uid() = user_id);
drop policy if exists "Users can view their own app settings." on public.app_settings;
create policy "Users can view their own app settings." on public.app_settings
for select using (auth.uid() = user_id);
drop policy if exists "Users can insert their own app settings." on public.app_settings;
create policy "Users can insert their own app settings." on public.app_settings
for insert with check (auth.uid() = user_id);
drop policy if exists "Users can update their own app settings." on public.app_settings;
create policy "Users can update their own app settings." on public.app_settings
for update using (auth.uid() = user_id);
drop policy if exists "Users can delete their own app settings." on public.app_settings;
create policy "Users can delete their own app settings." on public.app_settings
for delete using (auth.uid() = user_id);
drop trigger if exists set_clients_updated_at on public.clients;
create trigger set_clients_updated_at
before update on public.clients
for each row execute procedure public.set_updated_at();
drop trigger if exists set_projects_updated_at on public.projects;
create trigger set_projects_updated_at
before update on public.projects
for each row execute procedure public.set_updated_at();
drop trigger if exists set_calendar_events_updated_at on public.calendar_events;
create trigger set_calendar_events_updated_at
before update on public.calendar_events
for each row execute procedure public.set_updated_at();
drop trigger if exists set_finance_transactions_updated_at on public.finance_transactions;
create trigger set_finance_transactions_updated_at
before update on public.finance_transactions
for each row execute procedure public.set_updated_at();
drop trigger if exists set_daily_logs_updated_at on public.daily_logs;
create trigger set_daily_logs_updated_at
before update on public.daily_logs
for each row execute procedure public.set_updated_at();
drop trigger if exists set_app_settings_updated_at on public.app_settings;
create trigger set_app_settings_updated_at
before update on public.app_settings
for each row execute procedure public.set_updated_at();
@@ -0,0 +1,210 @@
-- Seed 0001: Demo Freelancer OS data
-- Run after: supabase/migrations/0002_add_freelancer_os_core_tables.sql
--
-- Before running, replace the UUID below with an existing auth.users.id.
-- This file is for local/demo environments. Do not run it on production data.
with seed_user as (
select '00000000-0000-0000-0000-000000000000'::uuid as user_id
),
demo_clients as (
insert into public.clients (user_id, name, company_name, email, status, notes)
select user_id, 'Acme Studio', 'Acme Studio LLC', 'hello@acme.example', 'active', 'Primary design and web retainer client.'
from seed_user
union all
select user_id, 'Northwind Labs', 'Northwind Labs', 'ops@northwind.example', 'active', 'Product strategy project with monthly milestones.'
from seed_user
returning id, user_id, name
),
demo_projects as (
insert into public.projects (user_id, client_id, name, type, description, status, start_date, due_date, budget_amount, currency, progress)
select
c.user_id,
c.id,
'Acme Website Refresh',
'client_project',
'Marketing website refresh and conversion improvements.',
'active',
current_date - 14,
current_date + 21,
4200,
'USD',
45
from demo_clients c
where c.name = 'Acme Studio'
union all
select
c.user_id,
c.id,
'Northwind Product Audit',
'client_project',
'Audit onboarding, analytics, and project roadmap.',
'active',
current_date - 7,
current_date + 14,
2800,
'USD',
30
from demo_clients c
where c.name = 'Northwind Labs'
union all
select
user_id,
null,
'Cognis Open Source Launch',
'side_project',
'Prepare the self-hosted Freelancer OS MVP for public release.',
'planning',
current_date,
current_date + 45,
null,
'USD',
10
from seed_user
returning id, user_id, client_id, name
),
demo_tasks as (
insert into public.tasks (user_id, client_id, project_id, title, description, status, priority, due_at, estimated_minutes, actual_minutes)
select
p.user_id,
p.client_id,
p.id,
'Prepare homepage wireframe',
'Create first-pass homepage structure for client review.',
'todo',
'high',
timezone('utc'::text, now()) + interval '2 days',
180,
null
from demo_projects p
where p.name = 'Acme Website Refresh'
union all
select
p.user_id,
p.client_id,
p.id,
'Review onboarding analytics',
'Summarize funnel drop-off points and quick wins.',
'in_progress',
'urgent',
timezone('utc'::text, now()) + interval '1 day',
120,
45
from demo_projects p
where p.name = 'Northwind Product Audit'
union all
select
p.user_id,
p.client_id,
p.id,
'Write MVP installation notes',
'Document self-host setup, env vars, and migration order.',
'todo',
'medium',
timezone('utc'::text, now()) + interval '5 days',
90,
null
from demo_projects p
where p.name = 'Cognis Open Source Launch'
returning id, user_id, client_id, project_id, title
),
demo_events as (
insert into public.calendar_events (user_id, client_id, project_id, task_id, title, description, type, starts_at, ends_at)
select
t.user_id,
t.client_id,
t.project_id,
t.id,
'Client review: homepage wireframe',
'Review initial direction with Acme.',
'meeting',
timezone('utc'::text, now()) + interval '3 days',
timezone('utc'::text, now()) + interval '3 days 1 hour'
from demo_tasks t
where t.title = 'Prepare homepage wireframe'
union all
select
t.user_id,
t.client_id,
t.project_id,
t.id,
'Deep work: analytics audit',
'Focus block for Northwind analysis.',
'focus',
timezone('utc'::text, now()) + interval '1 day',
timezone('utc'::text, now()) + interval '1 day 2 hours'
from demo_tasks t
where t.title = 'Review onboarding analytics'
returning id
),
demo_finance as (
insert into public.finance_transactions (user_id, client_id, project_id, type, amount, currency, transaction_date, category, payment_status, description)
select
p.user_id,
p.client_id,
p.id,
'income',
2100,
'USD',
current_date - 3,
'Project deposit',
'paid',
'Acme Website Refresh initial payment.'
from demo_projects p
where p.name = 'Acme Website Refresh'
union all
select
p.user_id,
p.client_id,
p.id,
'income',
2800,
'USD',
current_date + 10,
'Project payment',
'pending',
'Northwind audit payment due.'
from demo_projects p
where p.name = 'Northwind Product Audit'
union all
select
user_id,
null,
null,
'expense',
49,
'USD',
current_date,
'Software',
'paid',
'Monthly tool subscription.'
from seed_user
returning id
),
demo_daily_logs as (
insert into public.daily_logs (user_id, log_date, mood_score, energy_score, work_satisfaction_score, note)
select user_id, current_date - 2, 3, 3, 3, 'Mixed admin day with some client context switching.'
from seed_user
union all
select user_id, current_date - 1, 4, 5, 4, 'Strong focus block and clear project progress.'
from seed_user
union all
select user_id, current_date, 4, 4, 4, 'Good planning momentum for the week.'
from seed_user
on conflict (user_id, log_date) do update set
mood_score = excluded.mood_score,
energy_score = excluded.energy_score,
work_satisfaction_score = excluded.work_satisfaction_score,
note = excluded.note,
updated_at = timezone('utc'::text, now())
returning id
)
insert into public.app_settings (user_id, timezone, currency, ai_provider, ai_model)
select user_id, 'UTC', 'USD', 'ollama', 'llama3'
from seed_user
on conflict (user_id) do update set
timezone = excluded.timezone,
currency = excluded.currency,
ai_provider = excluded.ai_provider,
ai_model = excluded.ai_model,
updated_at = timezone('utc'::text, now());