Skip to main content
8Space uses PostgreSQL via Supabase with a comprehensive schema supporting multi-tenancy, projects, tasks, and collaboration features.

Entity Relationship Diagram

Core Tables

Multi-Tenancy Layer

create table public.tenants (
  id uuid primary key default gen_random_uuid(),
  name text not null check (char_length(name) between 1 and 120),
  slug text not null unique check (slug ~ '^[a-z0-9]+(?:-[a-z0-9]+)*$'),
  created_by uuid not null references public.profiles (id) on delete restrict,
  created_at timestamptz not null default now(),
  archived_at timestamptz
);

create index projects_tenant_idx on public.projects (tenant_id);
Purpose: Workspace/organization container for projectsKey Features:
  • URL-safe slug for routing (e.g., /app/:tenantSlug/projects)
  • Soft delete via archived_at
  • Unique constraint on slug

Project Management

create table public.projects (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants (id) on delete cascade,
  name text not null check (char_length(name) between 1 and 120),
  description text,
  created_by uuid not null references public.profiles (id) on delete restrict,
  created_at timestamptz not null default now(),
  archived_at timestamptz
);
Key Relationships:
  • Belongs to exactly one tenant
  • Has many tasks, workflow columns, labels
  • Soft delete via archived_at

Task Management

create type public.task_priority as enum ('p0', 'p1', 'p2');

create table public.tasks (
  id uuid primary key default gen_random_uuid(),
  project_id uuid not null references public.projects (id) on delete cascade,
  title text not null check (char_length(title) between 1 and 240),
  status_column_id uuid not null references public.workflow_columns (id) on delete restrict,
  start_date date,
  due_date date,
  priority public.task_priority not null default 'p1',
  order_rank numeric(18, 6) not null default 1000,
  description text,
  estimate numeric(10, 2),
  is_milestone boolean not null default false,
  completed_at timestamptz,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  constraint task_dates_valid check (
    due_date is null
    or start_date is null
    or due_date >= start_date
  )
);

create index tasks_project_column_rank_idx 
  on public.tasks (project_id, status_column_id, order_rank);
create index tasks_project_due_date_idx 
  on public.tasks (project_id, due_date);
Key Features:
  • Lexicographic order_rank for drag-and-drop ordering
  • Date validation constraint
  • Milestone support
  • Effort estimation

Database Functions

Tenant Management

create or replace function public.create_tenant_with_owner(
  p_name text,
  p_slug text default null
)
returns public.tenants
Purpose: Create new tenant workspace with auto-generated slugBehavior:
  • Normalizes slug from name if not provided
  • Handles slug conflicts with numeric suffixes
  • Automatically adds creator as owner
  • Returns created tenant record
Example:
select * from create_tenant_with_owner('Acme Corp', 'acme');
-- Returns: { id: uuid, name: 'Acme Corp', slug: 'acme', ... }
create or replace function public.normalize_tenant_slug(p_value text)
returns text
Purpose: Convert arbitrary text to URL-safe slugTransformations:
  • Lowercase conversion
  • Replace non-alphanumeric with hyphens
  • Remove leading/trailing hyphens
  • Fallback to ‘space’ if empty
Example:
select normalize_tenant_slug('Acme Corp LLC!');
-- Returns: 'acme-corp-llc'

Project Management

create or replace function public.create_project_with_defaults(
  p_tenant_slug text,
  p_name text,
  p_description text
)
returns uuid
Purpose: Create project with default workflow columnsBehavior:
  1. Validates tenant membership
  2. Creates project record
  3. Adds all tenant members as project members
  4. Creates default workflow columns (Backlog, To Do, In Progress, Done)
  5. Returns project ID
Role Mapping:
  • Creator → owner
  • Tenant owners/admins → editor
  • Tenant members → viewer
create or replace function public.dashboard_metrics(
  p_project_id uuid,
  p_days_window int default 14
)
returns jsonb
Purpose: Aggregate project analyticsReturns:
{
  "tasksByStatus": { "backlog": 5, "todo": 12, "in_progress": 8, "done": 43 },
  "overdueCount": 3,
  "dueThisWeek": 7,
  "workloadByAssignee": [
    { "userId": "...", "displayName": "Alice", "activeCount": 5 },
    { "userId": "...", "displayName": "Bob", "activeCount": 3 }
  ],
  "completionTrend": [
    { "date": "2026-02-20", "doneCount": 4 },
    { "date": "2026-02-21", "doneCount": 2 }
  ]
}

Task Operations

create or replace function public.move_task(
  p_task_id uuid,
  p_to_column_id uuid,
  p_new_rank numeric
)
returns public.tasks
Purpose: Move task between workflow columnsBehavior:
  1. Validates task and column existence
  2. Checks user permissions (can_edit_project)
  3. Updates status_column_id and order_rank
  4. Logs activity event
  5. Returns updated task
Used by: Drag-and-drop in Kanban board

Permission Helpers

-- Tenant-level permissions
create function public.current_tenant_role(p_tenant_id uuid) returns tenant_role;
create function public.is_tenant_member(p_tenant_id uuid) returns boolean;
create function public.is_tenant_owner(p_tenant_id uuid) returns boolean;

-- Project-level permissions
create function public.current_project_role(p_project_id uuid) returns project_role;
create function public.is_project_member(p_project_id uuid) returns boolean;
create function public.can_edit_project(p_project_id uuid) returns boolean;
create function public.is_project_owner(p_project_id uuid) returns boolean;
All permission functions are marked security definer and set search_path = public to prevent SQL injection and privilege escalation.

Row Level Security (RLS)

Tenant Policies

alter table public.tenants enable row level security;
alter table public.tenant_members enable row level security;

-- Users can only see tenants they belong to
create policy "tenants_select_members"
on public.tenants for select to authenticated
using (public.is_tenant_member(id));

-- Only owners can update/delete tenants
create policy "tenants_update_owner"
on public.tenants for update to authenticated
using (public.is_tenant_owner(id))
with check (public.is_tenant_owner(id));

Project Policies

alter table public.projects enable row level security;

-- Users can only see projects they're members of
create policy "projects_select_members"
on public.projects for select to authenticated
using (public.is_project_member(id));

-- Only editors can modify tasks
create policy "tasks_update_editors"
on public.tasks for update to authenticated
using (public.can_edit_project(project_id))
with check (public.can_edit_project(project_id));
RLS policies are automatically enforced on all queries, even from backend functions. This provides defense-in-depth security.

Task Policies

alter table public.tasks enable row level security;

-- Members can view all project tasks
create policy "tasks_select_members"
on public.tasks for select to authenticated
using (public.is_project_member(project_id));

-- Editors can create/update/delete tasks
create policy "tasks_insert_editors"
on public.tasks for insert to authenticated
with check (public.can_edit_project(project_id));

Triggers

create or replace function public.touch_updated_at()
returns trigger
language plpgsql
as $$
begin
  new.updated_at = now();
  return new;
end;
$$;

create trigger touch_tasks_updated_at
before update on public.tasks
for each row execute procedure public.touch_updated_at();
Applied to: profiles, workflow_columns, tasks, task_checklist_items

Indexes

create index tasks_project_column_rank_idx 
  on tasks (project_id, status_column_id, order_rank);

create index tasks_project_due_date_idx 
  on tasks (project_id, due_date);

Migration History

1

20260212220000_init_gantt_mvp.sql

Initial schema with projects, tasks, workflow columns, and basic RLS
2

20260213220000_auto_join_new_users.sql

Single-tenant mode: auto-add users to all projects
3

20260213220100_handle_new_user_google_compat.sql

Google OAuth metadata support for profile creation
4

20260216220000_fix_create_project_trigger_conflict.sql

Fixed duplicate project member insertions
5

20260217143000_multi_tenant_onboarding.sql

Multi-tenant architecture with tenant tables and updated functions

Next Steps

Multi-Tenancy Design

Deep dive into tenant isolation and permission model

Build docs developers (and LLMs) love