Budget Bee uses PostgreSQL 17.5 as its primary database, leveraging advanced features like row-level security, JSONB columns, and custom functions for secure multi-tenant data isolation.
CREATE ROLE auth_admin WITH LOGIN PASSWORD '<password>' BYPASSRLS;GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO auth_admin;REVOKE TRIGGER, TRUNCATE ON ALL TABLES IN SCHEMA public FROM auth_admin;
BYPASSRLS allows this role to bypass row-level security. Restrict network access to this user.
Permissions:
Full CRUD on auth tables (users, sessions, accounts, etc.)
CREATE ROLE subscription_admin WITH LOGIN PASSWORD '<password>' BYPASSRLS;GRANT SELECT ON users TO subscription_admin;GRANT ALL PRIVILEGES ON app_subscriptions TO subscription_admin;
CREATE ROLE anon NOLOGIN;CREATE ROLE authenticated NOLOGIN;GRANT USAGE ON SCHEMA public TO anon, authenticated;GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO authenticated;REVOKE TRIGGER, TRUNCATE ON ALL TABLES IN SCHEMA public FROM authenticated;
-- Userscreate table users ( id text primary key, name text not null, email text not null unique, email_verified boolean not null, image text, created_at timestamptz default CURRENT_TIMESTAMP not null, updated_at timestamptz default CURRENT_TIMESTAMP not null);-- Sessionscreate table sessions ( id text primary key, expires_at timestamptz not null, token text not null unique, user_id text not null references users(id) on delete cascade, active_organization_id text, ip_address text, user_agent text);-- Organizations (Multi-tenancy)create table organizations ( id text primary key, name text not null, slug text not null unique, logo text, created_at timestamptz not null, metadata text);-- Organization Memberscreate table members ( id text primary key, organization_id text not null references organizations(id) on delete cascade, user_id text not null references users(id) on delete cascade, role text not null, -- owner, admin, editor, viewer created_at timestamptz not null);
-- Subscription periodscreate type subscription_period as enum( 'monthly', 'yearly', 'quarterly', 'semi-annually', 'weekly', 'daily');-- Subscription statuscreate type subscription_status as enum('active', 'paused', 'canceled');-- Category colorscreate type builtin_colors as enum( 'gray', 'brown', 'orange', 'yellow', 'green', 'blue', 'purple', 'pink', 'red');-- Feature flag scopesCREATE TYPE feature_flag_scope AS ENUM('global', 'account', 'organization');
-- Extract user ID from JWTCREATE OR REPLACE FUNCTION uid() RETURNS text AS $$BEGIN RETURN COALESCE( current_setting('request.jwt.claims', true)::jsonb ->> 'sub', current_setting('request.jwt.claims', true)::jsonb ->> 'user_id' );END$$ LANGUAGE plpgsql STABLE;-- Extract organization ID from JWTCREATE OR REPLACE FUNCTION org_id() RETURNS text AS $$BEGIN RETURN current_setting('request.jwt.claims', true)::jsonb -> 'claims' ->> 'organization_id';END$$ LANGUAGE plpgsql STABLE;-- Extract organization role from JWTCREATE OR REPLACE FUNCTION org_role() RETURNS text AS $$BEGIN RETURN current_setting('request.jwt.claims', true)::jsonb -> 'claims' ->> 'organization_role';END$$ LANGUAGE plpgsql STABLE;
alter table transactions enable row level security;-- SELECT: Users see their own data or org data (if permitted)CREATE POLICY limit_transactions_select ON transactions FOR SELECT TO authenticated USING ( (organization_id IS NULL AND user_id = uid()) OR (organization_id = org_id() AND check_ac_current('transaction', 'list')) );-- INSERT: Users can create for themselves or their org (if permitted)CREATE POLICY limit_transactions_insert ON transactions FOR INSERT TO authenticated WITH CHECK ( (organization_id IS NULL AND user_id = uid()) OR (organization_id = org_id() AND check_ac_current('transaction', 'create')) );-- UPDATE: Same as INSERTCREATE POLICY limit_transactions_update ON transactions FOR UPDATE TO authenticated USING ( (organization_id IS NULL AND user_id = uid()) OR (organization_id = org_id() AND check_ac_current('transaction', 'update')) );-- DELETE: Requires delete permissionCREATE POLICY limit_transactions_delete ON transactions FOR DELETE TO authenticated USING ( (organization_id IS NULL AND user_id = uid()) OR (organization_id = org_id() AND check_ac_current('transaction', 'delete')) );
Categories, tags, and other tables use similar RLS patterns.
CREATE FUNCTION get_filtered_transactions(filters jsonb)RETURNS SETOF transactions AS $$-- Dynamically filters transactions based on:-- - amount (eq, gt, gte, lt, lte)-- - category (is, is not, is empty)-- - status (is, is not, is empty)-- - dates (from, to, between)$$;
CREATE FUNCTION delete_category( p_category_id UUID, p_cascade_delete BOOLEAN DEFAULT FALSE)RETURNS VOID AS $$-- Optionally delete all transactions in category$$;
CREATE OR REPLACE FUNCTION set_updated_at_timestamp()RETURNS TRIGGER AS $$BEGIN NEW.updated_at = NOW(); RETURN NEW;END;$$ LANGUAGE plpgsql;CREATE TRIGGER set_updated_at BEFORE UPDATE ON transactionsFOR EACH ROW EXECUTE FUNCTION set_updated_at_timestamp();
CREATE OR REPLACE FUNCTION create_default_categories()RETURNS TRIGGER AS $$BEGIN INSERT INTO categories (name, user_id) VALUES ('Food', NEW.id), ('Travel', NEW.id), ('Sales', NEW.id); RETURN NEW;END;$$ LANGUAGE plpgsql;CREATE TRIGGER on_user_insert_create_default_categoriesAFTER INSERT ON users FOR EACH ROWEXECUTE FUNCTION create_default_categories();
-- Transaction queriesCREATE INDEX idx_transactions_user_id ON transactions(user_id);CREATE INDEX idx_transactions_org_id ON transactions(organization_id);CREATE INDEX idx_transactions_date ON transactions(transaction_date);CREATE INDEX idx_transactions_category ON transactions(category_id);-- Feature flagsCREATE INDEX idx_feature_flags_key_scope_scope_id ON feature_flags(key, scope, scope_id);