192 lines
5.9 KiB
PL/PgSQL
192 lines
5.9 KiB
PL/PgSQL
-- ============================================================
|
|
-- Time Tracker App - Teams & Privileges Schema
|
|
-- Run this SQL in your Supabase SQL Editor
|
|
-- ============================================================
|
|
|
|
-- 1. Teams table
|
|
CREATE TABLE IF NOT EXISTS teams (
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL
|
|
);
|
|
|
|
-- 2. Team Members table (links users to teams with roles)
|
|
CREATE TABLE IF NOT EXISTS team_members (
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'manager', 'user')),
|
|
joined_at TIMESTAMPTZ DEFAULT now(),
|
|
UNIQUE(team_id, user_id)
|
|
);
|
|
|
|
-- 3. User Profiles table (extends auth.users with global role)
|
|
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
full_name TEXT DEFAULT '',
|
|
phone TEXT DEFAULT '',
|
|
company TEXT DEFAULT '',
|
|
avatar_url TEXT DEFAULT '',
|
|
global_role TEXT NOT NULL DEFAULT 'user' CHECK (global_role IN ('admin', 'manager', 'user')),
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- ============================================================
|
|
-- Indexes
|
|
-- ============================================================
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_team_id ON team_members(team_id);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_user_id ON team_members(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_profiles_global_role ON user_profiles(global_role);
|
|
|
|
-- ============================================================
|
|
-- Row Level Security (RLS)
|
|
-- ============================================================
|
|
|
|
-- Teams: anyone authenticated can read; only admins can insert/update/delete
|
|
ALTER TABLE teams ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "Teams are viewable by authenticated users"
|
|
ON teams FOR SELECT
|
|
TO authenticated
|
|
USING (true);
|
|
|
|
CREATE POLICY "Admins can insert teams"
|
|
ON teams FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role = 'admin'
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Admins can update teams"
|
|
ON teams FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role = 'admin'
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Admins can delete teams"
|
|
ON teams FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role = 'admin'
|
|
)
|
|
);
|
|
|
|
-- Team Members: authenticated users can read; admins/managers can manage
|
|
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "Team members are viewable by authenticated users"
|
|
ON team_members FOR SELECT
|
|
TO authenticated
|
|
USING (true);
|
|
|
|
CREATE POLICY "Admins can insert team members"
|
|
ON team_members FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role IN ('admin', 'manager')
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Admins can update team members"
|
|
ON team_members FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role IN ('admin', 'manager')
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Admins can delete team members"
|
|
ON team_members FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role IN ('admin', 'manager')
|
|
)
|
|
);
|
|
|
|
-- User Profiles: users can read all profiles, update their own; admins can update any
|
|
ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "User profiles are viewable by authenticated users"
|
|
ON user_profiles FOR SELECT
|
|
TO authenticated
|
|
USING (true);
|
|
|
|
CREATE POLICY "Users can update own profile"
|
|
ON user_profiles FOR UPDATE
|
|
TO authenticated
|
|
USING (auth.uid() = id);
|
|
|
|
CREATE POLICY "Admins can update any profile"
|
|
ON user_profiles FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM user_profiles
|
|
WHERE id = auth.uid() AND global_role = 'admin'
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Users can insert own profile"
|
|
ON user_profiles FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (auth.uid() = id);
|
|
|
|
-- ============================================================
|
|
-- Trigger: Auto-create user_profiles on signup
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
INSERT INTO public.user_profiles (id, full_name, avatar_url)
|
|
VALUES (
|
|
NEW.id,
|
|
COALESCE(NEW.raw_user_meta_data->>'full_name', ''),
|
|
COALESCE(NEW.raw_user_meta_data->>'avatar_url', '')
|
|
);
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
CREATE TRIGGER on_auth_user_created
|
|
AFTER INSERT ON auth.users
|
|
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
|
|
|
-- ============================================================
|
|
-- Trigger: Auto-update updated_at on user_profiles
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION public.update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS update_user_profiles_updated_at ON user_profiles;
|
|
CREATE TRIGGER update_user_profiles_updated_at
|
|
BEFORE UPDATE ON user_profiles
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at();
|
|
|
|
-- ============================================================
|
|
-- Seed: Make the first user an admin (adjust the user_id as needed)
|
|
-- ============================================================
|
|
-- UPDATE user_profiles SET global_role = 'admin' WHERE id = 'YOUR_USER_ID_HERE'; |