initial commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
-- v1 initial schema for linumiq tunnel platform
|
||||
-- Phase 2 / Wave A backend
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Tables
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.tunnels (
|
||||
user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
subdomain citext UNIQUE NOT NULL
|
||||
CHECK (subdomain ~ '^[a-z0-9-]{3,32}$'
|
||||
AND subdomain NOT IN ('app','api','www','admin','auth','mail','static')),
|
||||
token text UNIQUE NOT NULL,
|
||||
is_active boolean NOT NULL DEFAULT true,
|
||||
bytes_used bigint NOT NULL DEFAULT 0,
|
||||
quota_bytes bigint NOT NULL DEFAULT 1099511627776,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
last_seen_at timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.subscriptions (
|
||||
user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
plan text NOT NULL DEFAULT 'free',
|
||||
status text NOT NULL DEFAULT 'active',
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.usage_samples (
|
||||
id bigserial PRIMARY KEY,
|
||||
subdomain citext NOT NULL,
|
||||
ts timestamptz NOT NULL DEFAULT now(),
|
||||
bytes_delta bigint NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS usage_samples_subdomain_ts_idx
|
||||
ON public.usage_samples (subdomain, ts DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.users_profile (
|
||||
user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
email text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Trigger: auto-create users_profile row on auth.users insert
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.users_profile (user_id, email)
|
||||
VALUES (NEW.id, NEW.email)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
REVOKE ALL ON FUNCTION public.handle_new_user() FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.handle_new_user() TO supabase_auth_admin;
|
||||
|
||||
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();
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Row-level security
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
ALTER TABLE public.tunnels ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.subscriptions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.usage_samples ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.users_profile ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- tunnels: owner can SELECT and UPDATE. No INSERT/DELETE for users.
|
||||
DROP POLICY IF EXISTS tunnels_select_own ON public.tunnels;
|
||||
CREATE POLICY tunnels_select_own ON public.tunnels
|
||||
FOR SELECT TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
DROP POLICY IF EXISTS tunnels_update_own ON public.tunnels;
|
||||
CREATE POLICY tunnels_update_own ON public.tunnels
|
||||
FOR UPDATE TO authenticated
|
||||
USING (auth.uid() = user_id)
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- subscriptions: owner can SELECT.
|
||||
DROP POLICY IF EXISTS subscriptions_select_own ON public.subscriptions;
|
||||
CREATE POLICY subscriptions_select_own ON public.subscriptions
|
||||
FOR SELECT TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- usage_samples: no anon/authenticated access (service_role bypasses RLS).
|
||||
-- (No policies created → all access denied for non-service roles.)
|
||||
|
||||
-- users_profile: owner can SELECT and UPDATE.
|
||||
DROP POLICY IF EXISTS users_profile_select_own ON public.users_profile;
|
||||
CREATE POLICY users_profile_select_own ON public.users_profile
|
||||
FOR SELECT TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
DROP POLICY IF EXISTS users_profile_update_own ON public.users_profile;
|
||||
CREATE POLICY users_profile_update_own ON public.users_profile
|
||||
FOR UPDATE TO authenticated
|
||||
USING (auth.uid() = user_id)
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Grants: let PostgREST roles see the tables; RLS still gates access.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role;
|
||||
|
||||
GRANT SELECT, UPDATE ON public.tunnels TO authenticated;
|
||||
GRANT SELECT ON public.subscriptions TO authenticated;
|
||||
GRANT SELECT, UPDATE ON public.users_profile TO authenticated;
|
||||
|
||||
GRANT ALL ON public.tunnels, public.subscriptions, public.usage_samples, public.users_profile TO service_role;
|
||||
GRANT USAGE, SELECT ON SEQUENCE public.usage_samples_id_seq TO service_role;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user