mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
-- Create role enum
|
||||
CREATE TYPE public.app_role AS ENUM ('admin', 'manager', 'homeowner');
|
||||
|
||||
-- Create profiles table
|
||||
CREATE TABLE public.profiles (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
full_name TEXT,
|
||||
avatar_url TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create user_roles table
|
||||
CREATE TABLE public.user_roles (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
role app_role NOT NULL,
|
||||
UNIQUE (user_id, role)
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Profiles policies
|
||||
CREATE POLICY "Users can view all profiles" ON public.profiles FOR SELECT TO authenticated USING (true);
|
||||
CREATE POLICY "Users can update own profile" ON public.profiles FOR UPDATE TO authenticated USING (auth.uid() = user_id);
|
||||
CREATE POLICY "Users can insert own profile" ON public.profiles FOR INSERT TO authenticated WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Security definer function for role checks
|
||||
CREATE OR REPLACE FUNCTION public.has_role(_user_id uuid, _role app_role)
|
||||
RETURNS boolean
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM public.user_roles WHERE user_id = _user_id AND role = _role
|
||||
)
|
||||
$$;
|
||||
|
||||
-- User roles policies
|
||||
CREATE POLICY "Users can view own roles" ON public.user_roles FOR SELECT TO authenticated USING (auth.uid() = user_id);
|
||||
CREATE POLICY "Admins can manage roles" ON public.user_roles FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
|
||||
-- Auto-create profile and default homeowner role on signup
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (user_id, full_name)
|
||||
VALUES (NEW.id, NEW.raw_user_meta_data ->> 'full_name');
|
||||
|
||||
INSERT INTO public.user_roles (user_id, role)
|
||||
VALUES (NEW.id, 'homeowner');
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
|
||||
-- Updated_at trigger
|
||||
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SET search_path = public;
|
||||
|
||||
CREATE TRIGGER update_profiles_updated_at
|
||||
BEFORE UPDATE ON public.profiles
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,131 @@
|
||||
-- Associations table
|
||||
CREATE TABLE public.associations (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
city TEXT,
|
||||
state TEXT,
|
||||
zip TEXT,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
management_fee NUMERIC(10,2),
|
||||
fiscal_year_start INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Units table
|
||||
CREATE TABLE public.units (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
unit_number TEXT NOT NULL,
|
||||
address TEXT,
|
||||
city TEXT,
|
||||
state TEXT,
|
||||
zip TEXT,
|
||||
bedrooms INTEGER,
|
||||
bathrooms NUMERIC(3,1),
|
||||
sqft INTEGER,
|
||||
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'delinquent')),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Owners table
|
||||
CREATE TABLE public.owners (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
mailing_address TEXT,
|
||||
is_primary BOOLEAN DEFAULT false,
|
||||
is_tenant BOOLEAN DEFAULT false,
|
||||
move_in_date DATE,
|
||||
move_out_date DATE,
|
||||
balance NUMERIC(12,2) DEFAULT 0,
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Violations table
|
||||
CREATE TABLE public.violations (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
status TEXT DEFAULT 'open' CHECK (status IN ('open', 'pending', 'resolved', 'escalated', 'closed')),
|
||||
priority TEXT DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'critical')),
|
||||
due_date DATE,
|
||||
resolved_date DATE,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ARC Applications table
|
||||
CREATE TABLE public.arc_applications (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
project_type TEXT,
|
||||
estimated_cost NUMERIC(12,2),
|
||||
status TEXT DEFAULT 'submitted' CHECK (status IN ('submitted', 'under_review', 'approved', 'denied', 'withdrawn')),
|
||||
submitted_date DATE DEFAULT CURRENT_DATE,
|
||||
review_date DATE,
|
||||
decision_notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.associations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.units ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.owners ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.violations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.arc_applications ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS Policies (authenticated users can read all, admin/manager can write)
|
||||
CREATE POLICY "Authenticated users can view associations" ON public.associations FOR SELECT TO authenticated USING (true);
|
||||
CREATE POLICY "Admins can manage associations" ON public.associations FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
CREATE POLICY "Managers can manage associations" ON public.associations FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Authenticated users can view units" ON public.units FOR SELECT TO authenticated USING (true);
|
||||
CREATE POLICY "Admins can manage units" ON public.units FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
CREATE POLICY "Managers can manage units" ON public.units FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Authenticated users can view owners" ON public.owners FOR SELECT TO authenticated USING (true);
|
||||
CREATE POLICY "Admins can manage owners" ON public.owners FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
CREATE POLICY "Managers can manage owners" ON public.owners FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Authenticated users can view violations" ON public.violations FOR SELECT TO authenticated USING (true);
|
||||
CREATE POLICY "Admins can manage violations" ON public.violations FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
CREATE POLICY "Managers can manage violations" ON public.violations FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Authenticated users can view arc_applications" ON public.arc_applications FOR SELECT TO authenticated USING (true);
|
||||
CREATE POLICY "Admins can manage arc_applications" ON public.arc_applications FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
CREATE POLICY "Managers can manage arc_applications" ON public.arc_applications FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_units_association ON public.units(association_id);
|
||||
CREATE INDEX idx_owners_association ON public.owners(association_id);
|
||||
CREATE INDEX idx_owners_unit ON public.owners(unit_id);
|
||||
CREATE INDEX idx_violations_association ON public.violations(association_id);
|
||||
CREATE INDEX idx_violations_unit ON public.violations(unit_id);
|
||||
CREATE INDEX idx_arc_applications_association ON public.arc_applications(association_id);
|
||||
|
||||
-- Updated_at triggers
|
||||
CREATE TRIGGER update_associations_updated_at BEFORE UPDATE ON public.associations FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
CREATE TRIGGER update_units_updated_at BEFORE UPDATE ON public.units FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
CREATE TRIGGER update_owners_updated_at BEFORE UPDATE ON public.owners FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
CREATE TRIGGER update_violations_updated_at BEFORE UPDATE ON public.violations FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
CREATE TRIGGER update_arc_applications_updated_at BEFORE UPDATE ON public.arc_applications FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
-- Chart of Accounts
|
||||
CREATE TABLE public.chart_of_accounts (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
account_number TEXT NOT NULL,
|
||||
account_name TEXT NOT NULL,
|
||||
account_type TEXT NOT NULL DEFAULT 'expense',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.chart_of_accounts ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view chart_of_accounts"
|
||||
ON public.chart_of_accounts FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert chart_of_accounts"
|
||||
ON public.chart_of_accounts FOR INSERT TO authenticated WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can update chart_of_accounts"
|
||||
ON public.chart_of_accounts FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can delete chart_of_accounts"
|
||||
ON public.chart_of_accounts FOR DELETE TO authenticated USING (true);
|
||||
|
||||
-- Journal Entries
|
||||
CREATE TABLE public.journal_entries (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
chart_of_account_id UUID REFERENCES public.chart_of_accounts(id) ON DELETE SET NULL,
|
||||
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
description TEXT,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
type TEXT NOT NULL DEFAULT 'debit' CHECK (type IN ('debit', 'credit')),
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.journal_entries ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view journal_entries"
|
||||
ON public.journal_entries FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert journal_entries"
|
||||
ON public.journal_entries FOR INSERT TO authenticated WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can update journal_entries"
|
||||
ON public.journal_entries FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can delete journal_entries"
|
||||
ON public.journal_entries FOR DELETE TO authenticated USING (true);
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
CREATE TABLE public.billable_expenses (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
description TEXT,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
category TEXT,
|
||||
billable_type TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
is_credit BOOLEAN NOT NULL DEFAULT false,
|
||||
credit_reason TEXT,
|
||||
quantity NUMERIC(10,2) DEFAULT 1,
|
||||
unit_price NUMERIC(12,2),
|
||||
vendor_name TEXT,
|
||||
address TEXT,
|
||||
receipt_url TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.billable_expenses ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view billable_expenses"
|
||||
ON public.billable_expenses FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert billable_expenses"
|
||||
ON public.billable_expenses FOR INSERT TO authenticated WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can update billable_expenses"
|
||||
ON public.billable_expenses FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can delete billable_expenses"
|
||||
ON public.billable_expenses FOR DELETE TO authenticated USING (true);
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
CREATE TABLE public.board_members (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
member_name TEXT NOT NULL,
|
||||
member_email TEXT,
|
||||
phone TEXT,
|
||||
role TEXT DEFAULT 'Member',
|
||||
approval_authority BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.board_members ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view board_members"
|
||||
ON public.board_members FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert board_members"
|
||||
ON public.board_members FOR INSERT TO authenticated WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can update board_members"
|
||||
ON public.board_members FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can delete board_members"
|
||||
ON public.board_members FOR DELETE TO authenticated USING (true);
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
-- Announcements table
|
||||
CREATE TABLE public.announcements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
visibility TEXT NOT NULL DEFAULT 'all',
|
||||
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.announcements ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can read active announcements"
|
||||
ON public.announcements FOR SELECT TO authenticated
|
||||
USING (status = 'active');
|
||||
|
||||
CREATE POLICY "Authenticated users can insert announcements"
|
||||
ON public.announcements FOR INSERT TO authenticated
|
||||
WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authors can update their announcements"
|
||||
ON public.announcements FOR UPDATE TO authenticated
|
||||
USING (created_by = auth.uid());
|
||||
|
||||
CREATE POLICY "Authors can delete their announcements"
|
||||
ON public.announcements FOR DELETE TO authenticated
|
||||
USING (created_by = auth.uid());
|
||||
|
||||
-- Reminders table
|
||||
CREATE TABLE public.reminders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
due_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_by UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.reminders ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users can read own reminders"
|
||||
ON public.reminders FOR SELECT TO authenticated
|
||||
USING (created_by = auth.uid());
|
||||
|
||||
CREATE POLICY "Users can create reminders"
|
||||
ON public.reminders FOR INSERT TO authenticated
|
||||
WITH CHECK (created_by = auth.uid());
|
||||
|
||||
CREATE POLICY "Users can update own reminders"
|
||||
ON public.reminders FOR UPDATE TO authenticated
|
||||
USING (created_by = auth.uid());
|
||||
|
||||
CREATE POLICY "Users can delete own reminders"
|
||||
ON public.reminders FOR DELETE TO authenticated
|
||||
USING (created_by = auth.uid());
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
-- Unit Tenants
|
||||
CREATE TABLE public.unit_tenants (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE CASCADE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
lease_start DATE,
|
||||
lease_end DATE,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Unit Parking
|
||||
CREATE TABLE public.unit_parking (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE CASCADE NOT NULL,
|
||||
spaces TEXT,
|
||||
spots TEXT,
|
||||
type TEXT,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Unit Pets
|
||||
CREATE TABLE public.unit_pets (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE CASCADE NOT NULL,
|
||||
name TEXT,
|
||||
type TEXT,
|
||||
breed TEXT,
|
||||
weight TEXT,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Unit General Info
|
||||
CREATE TABLE public.unit_general_info (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE CASCADE NOT NULL,
|
||||
utilities TEXT,
|
||||
amenities TEXT,
|
||||
restrictions TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Unit Occupants
|
||||
CREATE TABLE public.unit_occupants (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE CASCADE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
relationship TEXT,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Enable RLS on all tables
|
||||
ALTER TABLE public.unit_tenants ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.unit_parking ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.unit_pets ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.unit_general_info ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.unit_occupants ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS policies for authenticated users
|
||||
CREATE POLICY "Authenticated users can manage unit_tenants" ON public.unit_tenants FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
CREATE POLICY "Authenticated users can manage unit_parking" ON public.unit_parking FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
CREATE POLICY "Authenticated users can manage unit_pets" ON public.unit_pets FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
CREATE POLICY "Authenticated users can manage unit_general_info" ON public.unit_general_info FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
CREATE POLICY "Authenticated users can manage unit_occupants" ON public.unit_occupants FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
@@ -0,0 +1,440 @@
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- CORE OPERATIONS TABLES
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
-- Projects
|
||||
CREATE TABLE public.projects (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
priority TEXT DEFAULT 'medium',
|
||||
start_date DATE,
|
||||
due_date DATE,
|
||||
budget NUMERIC(12,2),
|
||||
assigned_to TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Tasks
|
||||
CREATE TABLE public.tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
priority TEXT DEFAULT 'medium',
|
||||
due_date DATE,
|
||||
assigned_to UUID,
|
||||
project_id UUID REFERENCES public.projects(id) ON DELETE SET NULL,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Documents
|
||||
CREATE TABLE public.documents (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
category TEXT DEFAULT 'general',
|
||||
file_url TEXT,
|
||||
file_name TEXT,
|
||||
file_size BIGINT,
|
||||
uploaded_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Call Log
|
||||
CREATE TABLE public.call_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
caller_name TEXT NOT NULL,
|
||||
caller_phone TEXT,
|
||||
call_type TEXT DEFAULT 'inbound',
|
||||
subject TEXT,
|
||||
notes TEXT,
|
||||
follow_up_required BOOLEAN DEFAULT false,
|
||||
follow_up_date DATE,
|
||||
taken_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- FINANCIAL TABLES
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
-- Invoices
|
||||
CREATE TABLE public.invoices (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
vendor_name TEXT NOT NULL,
|
||||
invoice_number TEXT,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
issue_date DATE DEFAULT CURRENT_DATE,
|
||||
due_date DATE,
|
||||
paid_date DATE,
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Payments (admin-level tracking)
|
||||
CREATE TABLE public.admin_payments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
payment_method TEXT DEFAULT 'check',
|
||||
reference_number TEXT,
|
||||
payment_date DATE DEFAULT CURRENT_DATE,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'completed',
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Collections
|
||||
CREATE TABLE public.collections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
amount_owed NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
last_notice_date DATE,
|
||||
notes TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Payment Plans
|
||||
CREATE TABLE public.payment_plans (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
total_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
monthly_payment NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
notes TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Payables
|
||||
CREATE TABLE public.payables (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
vendor_name TEXT NOT NULL,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
due_date DATE,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
description TEXT,
|
||||
invoice_id UUID REFERENCES public.invoices(id) ON DELETE SET NULL,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- ADMINISTRATIVE TABLES
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
-- Inspections
|
||||
CREATE TABLE public.inspections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
inspection_type TEXT DEFAULT 'general',
|
||||
scheduled_date DATE,
|
||||
completed_date DATE,
|
||||
status TEXT NOT NULL DEFAULT 'scheduled',
|
||||
inspector TEXT,
|
||||
notes TEXT,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Blocked Dates
|
||||
CREATE TABLE public.blocked_dates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
reason TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Checklists
|
||||
CREATE TABLE public.checklists (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
items JSONB DEFAULT '[]'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- ASSOCIATION TABLES
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
-- Bids & Quotes
|
||||
CREATE TABLE public.bids_quotes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
vendor_name TEXT NOT NULL,
|
||||
project_id UUID REFERENCES public.projects(id) ON DELETE SET NULL,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
received_date DATE DEFAULT CURRENT_DATE,
|
||||
expiry_date DATE,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Bill Approvals
|
||||
CREATE TABLE public.bill_approvals (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
invoice_id UUID REFERENCES public.invoices(id) ON DELETE SET NULL,
|
||||
vendor_name TEXT NOT NULL,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
approved_by UUID,
|
||||
approved_date DATE,
|
||||
notes TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Client Requests
|
||||
CREATE TABLE public.client_requests (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
priority TEXT DEFAULT 'medium',
|
||||
requester_name TEXT,
|
||||
requester_email TEXT,
|
||||
assigned_to UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Homeowner Requests
|
||||
CREATE TABLE public.homeowner_requests (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
category TEXT DEFAULT 'general',
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
priority TEXT DEFAULT 'medium',
|
||||
assigned_to UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Estoppels
|
||||
CREATE TABLE public.estoppels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
||||
address TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'requested',
|
||||
requested_date DATE DEFAULT CURRENT_DATE,
|
||||
completed_date DATE,
|
||||
fee NUMERIC(12,2),
|
||||
requestor_name TEXT,
|
||||
requestor_email TEXT,
|
||||
notes TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Legal Matters
|
||||
CREATE TABLE public.legal_matters (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
case_number TEXT,
|
||||
attorney TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
category TEXT DEFAULT 'general',
|
||||
description TEXT,
|
||||
opened_date DATE DEFAULT CURRENT_DATE,
|
||||
closed_date DATE,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Status Updates
|
||||
CREATE TABLE public.status_updates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'published',
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Owner Updates
|
||||
CREATE TABLE public.owner_updates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT,
|
||||
visibility TEXT DEFAULT 'all',
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- ACCOUNTING TABLES
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
-- Bank Accounts
|
||||
CREATE TABLE public.bank_accounts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
account_name TEXT NOT NULL,
|
||||
account_number TEXT,
|
||||
routing_number TEXT,
|
||||
bank_name TEXT,
|
||||
account_type TEXT DEFAULT 'checking',
|
||||
current_balance NUMERIC(14,2) DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Budgets
|
||||
CREATE TABLE public.budgets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
fiscal_year INTEGER NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
budgeted_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
actual_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Checks
|
||||
CREATE TABLE public.checks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
bank_account_id UUID REFERENCES public.bank_accounts(id) ON DELETE SET NULL,
|
||||
check_number TEXT,
|
||||
payee TEXT NOT NULL,
|
||||
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
memo TEXT,
|
||||
check_date DATE DEFAULT CURRENT_DATE,
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
printed BOOLEAN DEFAULT false,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Calendar Events
|
||||
CREATE TABLE public.calendar_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
start_date TIMESTAMPTZ NOT NULL,
|
||||
end_date TIMESTAMPTZ,
|
||||
all_day BOOLEAN DEFAULT false,
|
||||
event_type TEXT DEFAULT 'meeting',
|
||||
location TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- ENABLE RLS ON ALL TABLES
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.tasks ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.call_logs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.invoices ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.admin_payments ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.collections ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.payment_plans ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.payables ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.inspections ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.blocked_dates ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.checklists ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.bids_quotes ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.bill_approvals ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.client_requests ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.homeowner_requests ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.estoppels ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.legal_matters ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.status_updates ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.owner_updates ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.bank_accounts ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.budgets ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.checks ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.calendar_events ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
-- RLS POLICIES - Admin/Manager full access for all tables
|
||||
-- ═══════════════════════════════════════════════════════════
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
BEGIN
|
||||
FOR tbl IN SELECT unnest(ARRAY[
|
||||
'projects','tasks','documents','call_logs','invoices','admin_payments',
|
||||
'collections','payment_plans','payables','inspections','blocked_dates',
|
||||
'checklists','bids_quotes','bill_approvals','client_requests',
|
||||
'homeowner_requests','estoppels','legal_matters','status_updates',
|
||||
'owner_updates','bank_accounts','budgets','checks','calendar_events'
|
||||
])
|
||||
LOOP
|
||||
EXECUTE format(
|
||||
'CREATE POLICY "Staff full access on %1$s" ON public.%1$s FOR ALL TO authenticated USING (public.has_role(auth.uid(), ''admin'') OR public.has_role(auth.uid(), ''manager'')) WITH CHECK (public.has_role(auth.uid(), ''admin'') OR public.has_role(auth.uid(), ''manager''))',
|
||||
tbl
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
@@ -0,0 +1,221 @@
|
||||
|
||||
-- Add columns to chart_of_accounts
|
||||
ALTER TABLE chart_of_accounts ADD COLUMN IF NOT EXISTS parent_account_id uuid REFERENCES chart_of_accounts(id);
|
||||
ALTER TABLE chart_of_accounts ADD COLUMN IF NOT EXISTS is_active boolean NOT NULL DEFAULT true;
|
||||
ALTER TABLE chart_of_accounts ADD COLUMN IF NOT EXISTS description text;
|
||||
|
||||
-- Add account_category to bank_accounts
|
||||
ALTER TABLE bank_accounts ADD COLUMN IF NOT EXISTS account_category text NOT NULL DEFAULT 'operating';
|
||||
|
||||
-- Create vendors table
|
||||
CREATE TABLE IF NOT EXISTS vendors (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
contact_name text,
|
||||
email text,
|
||||
phone text,
|
||||
address text,
|
||||
tax_id text,
|
||||
payment_terms text DEFAULT '30',
|
||||
default_expense_account_id uuid REFERENCES chart_of_accounts(id),
|
||||
is_active boolean NOT NULL DEFAULT true,
|
||||
notes text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create bank_transactions table (unified register)
|
||||
CREATE TABLE IF NOT EXISTS bank_transactions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
bank_account_id uuid NOT NULL REFERENCES bank_accounts(id) ON DELETE CASCADE,
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
transaction_type text NOT NULL DEFAULT 'payment',
|
||||
description text,
|
||||
reference_number text,
|
||||
debit numeric NOT NULL DEFAULT 0,
|
||||
credit numeric NOT NULL DEFAULT 0,
|
||||
is_cleared boolean NOT NULL DEFAULT false,
|
||||
cleared_date date,
|
||||
reconciliation_id uuid,
|
||||
related_entity_type text,
|
||||
related_entity_id uuid,
|
||||
created_by uuid,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create owner_ledger_entries
|
||||
CREATE TABLE IF NOT EXISTS owner_ledger_entries (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
owner_id uuid NOT NULL REFERENCES owners(id) ON DELETE CASCADE,
|
||||
unit_id uuid REFERENCES units(id),
|
||||
date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
transaction_type text NOT NULL,
|
||||
description text,
|
||||
debit numeric NOT NULL DEFAULT 0,
|
||||
credit numeric NOT NULL DEFAULT 0,
|
||||
reference_id uuid,
|
||||
reference_type text,
|
||||
created_by uuid,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create bills table (proper AP)
|
||||
CREATE TABLE IF NOT EXISTS bills (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
vendor_id uuid REFERENCES vendors(id),
|
||||
invoice_number text,
|
||||
bill_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
due_date date,
|
||||
amount numeric NOT NULL DEFAULT 0,
|
||||
amount_paid numeric NOT NULL DEFAULT 0,
|
||||
expense_account_id uuid REFERENCES chart_of_accounts(id),
|
||||
description text,
|
||||
status text NOT NULL DEFAULT 'draft',
|
||||
approved_by uuid,
|
||||
approved_date date,
|
||||
paid_date date,
|
||||
payment_method text,
|
||||
check_id uuid REFERENCES checks(id),
|
||||
attachment_url text,
|
||||
notes text,
|
||||
created_by uuid,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create deposit_batches
|
||||
CREATE TABLE IF NOT EXISTS deposit_batches (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
bank_account_id uuid NOT NULL REFERENCES bank_accounts(id),
|
||||
deposit_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
total_amount numeric NOT NULL DEFAULT 0,
|
||||
status text NOT NULL DEFAULT 'open',
|
||||
memo text,
|
||||
bank_transaction_id uuid,
|
||||
created_by uuid,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create deposit_batch_items
|
||||
CREATE TABLE IF NOT EXISTS deposit_batch_items (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
deposit_batch_id uuid NOT NULL REFERENCES deposit_batches(id) ON DELETE CASCADE,
|
||||
owner_ledger_entry_id uuid REFERENCES owner_ledger_entries(id),
|
||||
description text,
|
||||
amount numeric NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create bank_transfers
|
||||
CREATE TABLE IF NOT EXISTS bank_transfers (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
from_bank_account_id uuid NOT NULL REFERENCES bank_accounts(id),
|
||||
to_bank_account_id uuid NOT NULL REFERENCES bank_accounts(id),
|
||||
amount numeric NOT NULL,
|
||||
transfer_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
description text,
|
||||
from_transaction_id uuid,
|
||||
to_transaction_id uuid,
|
||||
created_by uuid,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Create bank_reconciliations
|
||||
CREATE TABLE IF NOT EXISTS bank_reconciliations (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
bank_account_id uuid NOT NULL REFERENCES bank_accounts(id),
|
||||
association_id uuid NOT NULL REFERENCES associations(id) ON DELETE CASCADE,
|
||||
statement_date date NOT NULL,
|
||||
opening_balance numeric NOT NULL DEFAULT 0,
|
||||
closing_balance numeric NOT NULL DEFAULT 0,
|
||||
cleared_balance numeric NOT NULL DEFAULT 0,
|
||||
difference numeric NOT NULL DEFAULT 0,
|
||||
status text NOT NULL DEFAULT 'in_progress',
|
||||
reconciled_by uuid,
|
||||
reconciled_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Add FK for bank_transactions reconciliation_id
|
||||
ALTER TABLE bank_transactions
|
||||
ADD CONSTRAINT bank_transactions_reconciliation_id_fkey
|
||||
FOREIGN KEY (reconciliation_id) REFERENCES bank_reconciliations(id);
|
||||
|
||||
-- Add FK for bank_transfers transaction ids
|
||||
ALTER TABLE bank_transfers
|
||||
ADD CONSTRAINT bank_transfers_from_transaction_id_fkey
|
||||
FOREIGN KEY (from_transaction_id) REFERENCES bank_transactions(id);
|
||||
ALTER TABLE bank_transfers
|
||||
ADD CONSTRAINT bank_transfers_to_transaction_id_fkey
|
||||
FOREIGN KEY (to_transaction_id) REFERENCES bank_transactions(id);
|
||||
|
||||
-- Add FK for deposit_batches bank_transaction_id
|
||||
ALTER TABLE deposit_batches
|
||||
ADD CONSTRAINT deposit_batches_bank_transaction_id_fkey
|
||||
FOREIGN KEY (bank_transaction_id) REFERENCES bank_transactions(id);
|
||||
|
||||
-- RLS for all new tables
|
||||
ALTER TABLE vendors ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE bank_transactions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE owner_ledger_entries ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE bills ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE deposit_batches ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE deposit_batch_items ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE bank_transfers ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE bank_reconciliations ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff access policies
|
||||
CREATE POLICY "Staff full access on vendors" ON vendors FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Staff full access on bank_transactions" ON bank_transactions FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Staff full access on owner_ledger_entries" ON owner_ledger_entries FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Owners can view own ledger" ON owner_ledger_entries FOR SELECT TO authenticated
|
||||
USING (EXISTS (SELECT 1 FROM owners WHERE owners.id = owner_ledger_entries.owner_id AND owners.user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Staff full access on bills" ON bills FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Staff full access on deposit_batches" ON deposit_batches FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Staff full access on deposit_batch_items" ON deposit_batch_items FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Staff full access on bank_transfers" ON bank_transfers FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Staff full access on bank_reconciliations" ON bank_reconciliations FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
-- Updated_at triggers for new tables
|
||||
CREATE TRIGGER update_vendors_updated_at BEFORE UPDATE ON vendors FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_bank_transactions_updated_at BEFORE UPDATE ON bank_transactions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_owner_ledger_entries_updated_at BEFORE UPDATE ON owner_ledger_entries FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_bills_updated_at BEFORE UPDATE ON bills FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_deposit_batches_updated_at BEFORE UPDATE ON deposit_batches FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_bank_transfers_updated_at BEFORE UPDATE ON bank_transfers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_bank_reconciliations_updated_at BEFORE UPDATE ON bank_reconciliations FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO public.user_roles (user_id, role)
|
||||
VALUES ('4b811784-cdc5-4e1b-9704-509a7ee5e3ee', 'admin')
|
||||
ON CONFLICT (user_id, role) DO NOTHING;
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS zoho_contact_id text;
|
||||
ALTER TABLE public.owners ADD COLUMN IF NOT EXISTS zoho_contact_id text;
|
||||
ALTER TABLE public.vendors ADD COLUMN IF NOT EXISTS zoho_contact_id text;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE public.owner_ledger_entries ADD COLUMN IF NOT EXISTS zoho_invoice_id text;
|
||||
ALTER TABLE public.admin_payments ADD COLUMN IF NOT EXISTS zoho_payment_id text;
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
ALTER TABLE public.owners
|
||||
ADD COLUMN IF NOT EXISTS account_number text,
|
||||
ADD COLUMN IF NOT EXISTS property_address text,
|
||||
ADD COLUMN IF NOT EXISTS alternate_address_1 text,
|
||||
ADD COLUMN IF NOT EXISTS alternate_address_2 text,
|
||||
ADD COLUMN IF NOT EXISTS electronic_consent boolean DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS show_proxy_text boolean DEFAULT true,
|
||||
ADD COLUMN IF NOT EXISTS exclude_from_signin boolean DEFAULT false;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS zoho_organization_id text;
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
-- Create in_app_notifications table
|
||||
CREATE TABLE public.in_app_notifications (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'info',
|
||||
title TEXT NOT NULL,
|
||||
message TEXT,
|
||||
is_read BOOLEAN NOT NULL DEFAULT false,
|
||||
related_item_id UUID,
|
||||
related_item_type TEXT,
|
||||
link TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.in_app_notifications ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Users can read their own notifications
|
||||
CREATE POLICY "Users can read own notifications"
|
||||
ON public.in_app_notifications FOR SELECT
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
|
||||
-- Users can update (mark as read) their own notifications
|
||||
CREATE POLICY "Users can update own notifications"
|
||||
ON public.in_app_notifications FOR UPDATE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (user_id = auth.uid());
|
||||
|
||||
-- Users can delete their own notifications
|
||||
CREATE POLICY "Users can delete own notifications"
|
||||
ON public.in_app_notifications FOR DELETE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
|
||||
-- Admins/managers can insert notifications for any user
|
||||
CREATE POLICY "Staff can insert notifications"
|
||||
ON public.in_app_notifications FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR user_id = auth.uid());
|
||||
|
||||
-- Enable realtime
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.in_app_notifications;
|
||||
|
||||
-- Add last_notified_at to reminders if not exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='reminders' AND column_name='last_notified_at') THEN
|
||||
ALTER TABLE public.reminders ADD COLUMN last_notified_at TIMESTAMP WITH TIME ZONE;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='reminders' AND column_name='assigned_to') THEN
|
||||
ALTER TABLE public.reminders ADD COLUMN assigned_to UUID;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Create a security definer function to insert notifications (bypasses RLS for edge functions)
|
||||
CREATE OR REPLACE FUNCTION public.insert_notification(
|
||||
p_user_id UUID,
|
||||
p_type TEXT,
|
||||
p_title TEXT,
|
||||
p_message TEXT DEFAULT NULL,
|
||||
p_related_item_id UUID DEFAULT NULL,
|
||||
p_related_item_type TEXT DEFAULT NULL,
|
||||
p_link TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS UUID
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_id UUID;
|
||||
BEGIN
|
||||
INSERT INTO public.in_app_notifications (user_id, type, title, message, related_item_id, related_item_type, link)
|
||||
VALUES (p_user_id, p_type, p_title, p_message, p_related_item_id, p_related_item_type, p_link)
|
||||
RETURNING id INTO v_id;
|
||||
RETURN v_id;
|
||||
END;
|
||||
$$;
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pg_cron WITH SCHEMA pg_catalog;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_net WITH SCHEMA extensions;
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
CREATE TABLE public.association_faqs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
question TEXT NOT NULL,
|
||||
answer TEXT,
|
||||
sort_order INT DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.association_faqs ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view association FAQs"
|
||||
ON public.association_faqs FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert association FAQs"
|
||||
ON public.association_faqs FOR INSERT TO authenticated WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can update association FAQs"
|
||||
ON public.association_faqs FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can delete association FAQs"
|
||||
ON public.association_faqs FOR DELETE TO authenticated USING (true);
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
CREATE TABLE public.saved_form_templates (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
form_type text NOT NULL,
|
||||
name text NOT NULL,
|
||||
association_id uuid REFERENCES public.associations(id) ON DELETE SET NULL,
|
||||
form_data jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.saved_form_templates ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on saved_form_templates"
|
||||
ON public.saved_form_templates
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
CREATE TRIGGER update_saved_form_templates_updated_at
|
||||
BEFORE UPDATE ON public.saved_form_templates
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
-- Create violation_responses table for homeowner QR code responses
|
||||
CREATE TABLE public.violation_responses (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
violation_id UUID NOT NULL REFERENCES public.violations(id) ON DELETE CASCADE,
|
||||
respondent_name TEXT,
|
||||
respondent_email TEXT,
|
||||
respondent_phone TEXT,
|
||||
response_text TEXT,
|
||||
date_corrected DATE,
|
||||
photo_urls JSONB DEFAULT '[]'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'submitted',
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.violation_responses ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Allow anyone to INSERT (public form from QR code, no auth required)
|
||||
CREATE POLICY "Anyone can submit violation responses"
|
||||
ON public.violation_responses
|
||||
FOR INSERT
|
||||
TO anon, authenticated
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Staff can view all responses
|
||||
CREATE POLICY "Staff can view violation responses"
|
||||
ON public.violation_responses
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Staff can update/delete responses
|
||||
CREATE POLICY "Staff can manage violation responses"
|
||||
ON public.violation_responses
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Allow anonymous users to read violation basic info for the response page
|
||||
CREATE POLICY "Anyone can read violations for response page"
|
||||
ON public.violations
|
||||
FOR SELECT
|
||||
TO anon
|
||||
USING (true);
|
||||
|
||||
-- Update trigger
|
||||
CREATE TRIGGER update_violation_responses_updated_at
|
||||
BEFORE UPDATE ON public.violation_responses
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
-- Add missing columns to tasks table
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS client_id uuid REFERENCES public.associations(id);
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS parent_task_id uuid REFERENCES public.tasks(id) ON DELETE CASCADE;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS template_id uuid;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS completed_at timestamptz;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS client_specific boolean DEFAULT false;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS recurring boolean DEFAULT false;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS recurring_interval text;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS recurring_next_date date;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS property_id uuid;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS task_sequence integer;
|
||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS days_until_due integer;
|
||||
|
||||
-- Create workflow_templates table
|
||||
CREATE TABLE IF NOT EXISTS public.workflow_templates (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
description text,
|
||||
client_id uuid REFERENCES public.associations(id),
|
||||
created_by uuid,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
updated_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.workflow_templates ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can manage workflow_templates"
|
||||
ON public.workflow_templates FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Create workflow_template_tasks table
|
||||
CREATE TABLE IF NOT EXISTS public.workflow_template_tasks (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
template_id uuid REFERENCES public.workflow_templates(id) ON DELETE CASCADE NOT NULL,
|
||||
task_name text NOT NULL,
|
||||
task_description text DEFAULT '',
|
||||
days_until_due integer DEFAULT 0,
|
||||
task_sequence integer DEFAULT 1,
|
||||
priority text DEFAULT 'medium',
|
||||
assigned_to text,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.workflow_template_tasks ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can manage workflow_template_tasks"
|
||||
ON public.workflow_template_tasks FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Update template_id FK now that workflow_templates exists
|
||||
ALTER TABLE public.tasks DROP CONSTRAINT IF EXISTS tasks_template_id_fkey;
|
||||
ALTER TABLE public.tasks ADD CONSTRAINT tasks_template_id_fkey FOREIGN KEY (template_id) REFERENCES public.workflow_templates(id);
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
-- Create violation_types table for configurable violation categories per association
|
||||
CREATE TABLE IF NOT EXISTS public.violation_types (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
category text NOT NULL,
|
||||
description text,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
updated_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.violation_types ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can manage violation_types"
|
||||
ON public.violation_types FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Add missing columns to violations for inspection workflow
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS address text;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS violation_type text;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS violation_date date DEFAULT CURRENT_DATE;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS notes text;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS property_id uuid;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.units ADD COLUMN IF NOT EXISTS zoho_customer_number text DEFAULT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.owners;
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
-- Add staff and employee roles to the enum
|
||||
ALTER TYPE public.app_role ADD VALUE IF NOT EXISTS 'staff';
|
||||
ALTER TYPE public.app_role ADD VALUE IF NOT EXISTS 'employee';
|
||||
|
||||
-- Add is_blocked column to profiles for blocking access
|
||||
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS is_blocked boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS email text;
|
||||
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS phone text;
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
-- Add logo_url to associations table
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS logo_url text;
|
||||
|
||||
-- Create company_settings table for company-level branding
|
||||
CREATE TABLE IF NOT EXISTS public.company_settings (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
key text NOT NULL UNIQUE,
|
||||
value text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.company_settings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on company_settings"
|
||||
ON public.company_settings
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
CREATE POLICY "Authenticated users can view company_settings"
|
||||
ON public.company_settings
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- Create public logos storage bucket
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('logos', 'logos', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Allow authenticated users to upload to logos bucket
|
||||
CREATE POLICY "Authenticated users can upload logos"
|
||||
ON storage.objects
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (bucket_id = 'logos');
|
||||
|
||||
-- Allow public read access to logos
|
||||
CREATE POLICY "Public read access to logos"
|
||||
ON storage.objects
|
||||
FOR SELECT
|
||||
TO public
|
||||
USING (bucket_id = 'logos');
|
||||
|
||||
-- Allow authenticated users to update/delete logos
|
||||
CREATE POLICY "Authenticated users can manage logos"
|
||||
ON storage.objects
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (bucket_id = 'logos');
|
||||
|
||||
CREATE POLICY "Authenticated users can update logos"
|
||||
ON storage.objects
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (bucket_id = 'logos');
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
-- Add new columns to owner_updates table
|
||||
ALTER TABLE public.owner_updates
|
||||
ADD COLUMN IF NOT EXISTS unit_id uuid REFERENCES public.units(id),
|
||||
ADD COLUMN IF NOT EXISTS posted_at timestamptz DEFAULT now(),
|
||||
ADD COLUMN IF NOT EXISTS attachments jsonb DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS collection_ids jsonb DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS violation_ids jsonb DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS tags jsonb DEFAULT '[]'::jsonb;
|
||||
|
||||
-- Create owner_update_tags table
|
||||
CREATE TABLE IF NOT EXISTS public.owner_update_tags (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
color text NOT NULL DEFAULT 'blue',
|
||||
association_id uuid REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
UNIQUE(name, association_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.owner_update_tags ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on owner_update_tags"
|
||||
ON public.owner_update_tags
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Create storage bucket for owner update attachments
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('owner-update-attachments', 'owner-update-attachments', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Storage policies for owner update attachments
|
||||
CREATE POLICY "Authenticated users can upload owner update attachments"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'owner-update-attachments');
|
||||
|
||||
CREATE POLICY "Anyone can view owner update attachments"
|
||||
ON storage.objects FOR SELECT TO authenticated
|
||||
USING (bucket_id = 'owner-update-attachments');
|
||||
|
||||
CREATE POLICY "Staff can delete owner update attachments"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (bucket_id = 'owner-update-attachments' AND (
|
||||
(SELECT has_role(auth.uid(), 'admin'::app_role)) OR
|
||||
(SELECT has_role(auth.uid(), 'manager'::app_role))
|
||||
));
|
||||
@@ -0,0 +1,71 @@
|
||||
-- Add new columns to status_updates
|
||||
ALTER TABLE public.status_updates
|
||||
ADD COLUMN IF NOT EXISTS image_urls jsonb DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS requested_action text,
|
||||
ADD COLUMN IF NOT EXISTS voting_enabled boolean DEFAULT false;
|
||||
|
||||
-- Create status_update_comments table
|
||||
CREATE TABLE IF NOT EXISTS public.status_update_comments (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
status_update_id uuid NOT NULL REFERENCES public.status_updates(id) ON DELETE CASCADE,
|
||||
user_id uuid NOT NULL,
|
||||
content text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.status_update_comments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view status_update_comments"
|
||||
ON public.status_update_comments FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert status_update_comments"
|
||||
ON public.status_update_comments FOR INSERT TO authenticated
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can delete own comments"
|
||||
ON public.status_update_comments FOR DELETE TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
|
||||
-- Create status_update_votes table
|
||||
CREATE TABLE IF NOT EXISTS public.status_update_votes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
status_update_id uuid NOT NULL REFERENCES public.status_updates(id) ON DELETE CASCADE,
|
||||
user_id uuid NOT NULL,
|
||||
vote text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (status_update_id, user_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.status_update_votes ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view status_update_votes"
|
||||
ON public.status_update_votes FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert status_update_votes"
|
||||
ON public.status_update_votes FOR INSERT TO authenticated
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can update own votes"
|
||||
ON public.status_update_votes FOR UPDATE TO authenticated
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (user_id = auth.uid());
|
||||
|
||||
-- Create storage bucket for status update images
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('status-update-images', 'status-update-images', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Storage policies
|
||||
CREATE POLICY "Authenticated users can upload status update images"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'status-update-images');
|
||||
|
||||
CREATE POLICY "Anyone can view status update images"
|
||||
ON storage.objects FOR SELECT TO public
|
||||
USING (bucket_id = 'status-update-images');
|
||||
|
||||
CREATE POLICY "Authenticated users can delete status update images"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (bucket_id = 'status-update-images');
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
-- Unit Parking
|
||||
CREATE TABLE IF NOT EXISTS public.unit_parking (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
unit_id UUID NOT NULL REFERENCES public.units(id) ON DELETE CASCADE,
|
||||
spaces TEXT,
|
||||
spots TEXT,
|
||||
type TEXT,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.unit_parking ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on unit_parking" ON public.unit_parking
|
||||
FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Unit Pets
|
||||
CREATE TABLE IF NOT EXISTS public.unit_pets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
unit_id UUID NOT NULL REFERENCES public.units(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
type TEXT DEFAULT '',
|
||||
breed TEXT DEFAULT '',
|
||||
weight TEXT DEFAULT '',
|
||||
notes TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.unit_pets ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on unit_pets" ON public.unit_pets
|
||||
FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Unit General Info
|
||||
CREATE TABLE IF NOT EXISTS public.unit_general_info (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
unit_id UUID NOT NULL REFERENCES public.units(id) ON DELETE CASCADE,
|
||||
utilities TEXT DEFAULT '',
|
||||
amenities TEXT DEFAULT '',
|
||||
restrictions TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.unit_general_info ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on unit_general_info" ON public.unit_general_info
|
||||
FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Unit Occupants
|
||||
CREATE TABLE IF NOT EXISTS public.unit_occupants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
unit_id UUID NOT NULL REFERENCES public.units(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
relationship TEXT NOT NULL DEFAULT '',
|
||||
phone TEXT DEFAULT '',
|
||||
email TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.unit_occupants ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on unit_occupants" ON public.unit_occupants
|
||||
FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
@@ -0,0 +1,114 @@
|
||||
|
||||
-- Zoho account mappings (local COA -> Zoho account)
|
||||
CREATE TABLE public.zoho_account_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
chart_of_account_id UUID NOT NULL REFERENCES public.chart_of_accounts(id) ON DELETE CASCADE,
|
||||
zoho_account_id TEXT NOT NULL,
|
||||
zoho_account_name TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(association_id, chart_of_account_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.zoho_account_mappings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on zoho_account_mappings"
|
||||
ON public.zoho_account_mappings FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Zoho customer mappings (owner/unit -> Zoho customer)
|
||||
CREATE TABLE public.zoho_customer_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE CASCADE,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE CASCADE,
|
||||
zoho_customer_id TEXT NOT NULL,
|
||||
zoho_customer_name TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(association_id, owner_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.zoho_customer_mappings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on zoho_customer_mappings"
|
||||
ON public.zoho_customer_mappings FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Zoho sync settings per association (toggle which entry types sync)
|
||||
CREATE TABLE public.zoho_sync_settings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE UNIQUE,
|
||||
sync_invoices BOOLEAN NOT NULL DEFAULT true,
|
||||
sync_payments BOOLEAN NOT NULL DEFAULT true,
|
||||
sync_contacts BOOLEAN NOT NULL DEFAULT true,
|
||||
sync_bills BOOLEAN NOT NULL DEFAULT false,
|
||||
sync_journal_entries BOOLEAN NOT NULL DEFAULT false,
|
||||
auto_sync_enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.zoho_sync_settings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on zoho_sync_settings"
|
||||
ON public.zoho_sync_settings FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Zoho sync log for tracking sync history and errors
|
||||
CREATE TABLE public.zoho_sync_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
sync_type TEXT NOT NULL, -- 'invoice', 'payment', 'contact', 'bill', etc.
|
||||
direction TEXT NOT NULL DEFAULT 'push', -- 'push' or 'pull'
|
||||
status TEXT NOT NULL DEFAULT 'success', -- 'success', 'error', 'partial'
|
||||
record_count INTEGER DEFAULT 0,
|
||||
error_message TEXT,
|
||||
details JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.zoho_sync_log ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on zoho_sync_log"
|
||||
ON public.zoho_sync_log FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Association fee rules (interest & late fee configuration per association)
|
||||
CREATE TABLE public.association_fee_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE UNIQUE,
|
||||
-- Interest settings
|
||||
interest_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
interest_rate NUMERIC NOT NULL DEFAULT 0, -- APR percentage e.g. 18.0
|
||||
interest_grace_days INTEGER NOT NULL DEFAULT 30,
|
||||
interest_compound TEXT NOT NULL DEFAULT 'monthly', -- 'monthly', 'daily', 'quarterly'
|
||||
interest_apply_to TEXT NOT NULL DEFAULT 'assessments', -- 'assessments', 'all_charges', 'assessments_and_fees'
|
||||
-- Late fee settings
|
||||
late_fee_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
late_fee_type TEXT NOT NULL DEFAULT 'flat', -- 'flat' or 'percentage'
|
||||
late_fee_amount NUMERIC NOT NULL DEFAULT 0, -- dollar amount or percentage
|
||||
late_fee_trigger_days INTEGER NOT NULL DEFAULT 15, -- days past due before late fee applies
|
||||
late_fee_max NUMERIC, -- optional max cap for percentage-based fees
|
||||
late_fee_recurring BOOLEAN NOT NULL DEFAULT false, -- apply every month?
|
||||
-- Schedule
|
||||
auto_apply_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
auto_apply_schedule TEXT NOT NULL DEFAULT 'monthly', -- 'monthly', 'quarterly'
|
||||
auto_apply_day INTEGER NOT NULL DEFAULT 1, -- day of month
|
||||
-- Push to Zoho
|
||||
push_to_zoho BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.association_fee_rules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on association_fee_rules"
|
||||
ON public.association_fee_rules FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE public.zoho_reporting_tag_mappings (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id uuid NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
zoho_tag_id text NOT NULL,
|
||||
zoho_tag_option_id text NOT NULL,
|
||||
zoho_tag_name text,
|
||||
zoho_option_name text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE(association_id, zoho_tag_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.zoho_reporting_tag_mappings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on zoho_reporting_tag_mappings"
|
||||
ON public.zoho_reporting_tag_mappings
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
-- Add missing columns to violations table for full violation management
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS stage text;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS photo_url text;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS photo_urls jsonb;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS notice_level text;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS notice_history jsonb;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS timeline_entries jsonb;
|
||||
ALTER TABLE public.violations ADD COLUMN IF NOT EXISTS assigned_to uuid;
|
||||
|
||||
-- Add missing columns to violation_types table
|
||||
ALTER TABLE public.violation_types ADD COLUMN IF NOT EXISTS article_section text;
|
||||
ALTER TABLE public.violation_types ADD COLUMN IF NOT EXISTS citation text;
|
||||
ALTER TABLE public.violation_types ADD COLUMN IF NOT EXISTS requested_action text;
|
||||
|
||||
-- Create violation-photos storage bucket
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('violation-photos', 'violation-photos', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Allow authenticated users to upload to violation-photos
|
||||
CREATE POLICY "Authenticated users can upload violation photos" ON storage.objects
|
||||
FOR INSERT TO authenticated WITH CHECK (bucket_id = 'violation-photos');
|
||||
|
||||
-- Allow public read access to violation photos
|
||||
CREATE POLICY "Public read access for violation photos" ON storage.objects
|
||||
FOR SELECT TO public USING (bucket_id = 'violation-photos');
|
||||
|
||||
-- Allow authenticated users to delete their violation photos
|
||||
CREATE POLICY "Authenticated users can delete violation photos" ON storage.objects
|
||||
FOR DELETE TO authenticated USING (bucket_id = 'violation-photos');
|
||||
@@ -0,0 +1,130 @@
|
||||
|
||||
-- Email Senders (SMTP sender identities)
|
||||
CREATE TABLE public.email_senders (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
sender_name TEXT NOT NULL,
|
||||
email_address TEXT NOT NULL,
|
||||
smtp_host TEXT,
|
||||
smtp_port INTEGER DEFAULT 587,
|
||||
smtp_username TEXT,
|
||||
smtp_password TEXT,
|
||||
use_tls BOOLEAN DEFAULT true,
|
||||
use_ssl BOOLEAN DEFAULT false,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
verified BOOLEAN DEFAULT false,
|
||||
is_default BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.email_senders ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Users manage own senders" ON public.email_senders FOR ALL TO authenticated USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid());
|
||||
|
||||
-- Email History
|
||||
CREATE TABLE public.email_history (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
sender_email TEXT,
|
||||
recipient_email TEXT,
|
||||
subject TEXT,
|
||||
body_text TEXT,
|
||||
sent_at TIMESTAMPTZ DEFAULT now(),
|
||||
status TEXT DEFAULT 'sent',
|
||||
feature_type TEXT,
|
||||
email_headers JSONB,
|
||||
proof_url TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.email_history ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Users view own email history" ON public.email_history FOR ALL TO authenticated USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid());
|
||||
|
||||
-- Email Routing Rules (inbound email addresses per association)
|
||||
CREATE TABLE public.email_routing_rules (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
email_address TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID
|
||||
);
|
||||
ALTER TABLE public.email_routing_rules ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Authenticated users manage routing" ON public.email_routing_rules FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Email Templates (reusable template library)
|
||||
CREATE TABLE public.email_templates (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
subject TEXT,
|
||||
body_html TEXT,
|
||||
thumbnail_url TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.email_templates ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Authenticated users manage templates" ON public.email_templates FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Notify Board Templates
|
||||
CREATE TABLE public.notify_board_templates (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
subject TEXT,
|
||||
body TEXT,
|
||||
attachments JSONB,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.notify_board_templates ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Authenticated users manage board templates" ON public.notify_board_templates FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Owner Notification Templates
|
||||
CREATE TABLE public.owner_notification_templates (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
subject TEXT,
|
||||
body TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.owner_notification_templates ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Authenticated users manage notification templates" ON public.owner_notification_templates FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Owner Notification Proofs
|
||||
CREATE TABLE public.owner_notification_proofs (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
client_id UUID,
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE SET NULL,
|
||||
subject TEXT,
|
||||
body TEXT,
|
||||
owners_notified JSONB,
|
||||
attachments JSONB,
|
||||
delivery_status TEXT,
|
||||
validation_id TEXT DEFAULT gen_random_uuid()::TEXT,
|
||||
proof_url TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.owner_notification_proofs ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Authenticated users manage proofs" ON public.owner_notification_proofs FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
|
||||
-- Email Server Settings (per association SMTP/IMAP config)
|
||||
CREATE TABLE public.email_server_settings (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
smtp_host TEXT,
|
||||
smtp_port INTEGER,
|
||||
smtp_username TEXT,
|
||||
smtp_password TEXT,
|
||||
imap_host TEXT,
|
||||
imap_port INTEGER,
|
||||
pop3_host TEXT,
|
||||
pop3_port INTEGER,
|
||||
is_configured BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(association_id)
|
||||
);
|
||||
ALTER TABLE public.email_server_settings ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Authenticated users manage server settings" ON public.email_server_settings FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
||||
@@ -0,0 +1,102 @@
|
||||
|
||||
-- Project comments/discussion table
|
||||
CREATE TABLE public.project_comments (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
project_id UUID NOT NULL REFERENCES public.projects(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.project_comments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Project files table
|
||||
CREATE TABLE public.project_files (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
project_id UUID NOT NULL REFERENCES public.projects(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_url TEXT NOT NULL,
|
||||
file_size BIGINT,
|
||||
mime_type TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.project_files ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Storage bucket for project files
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('project-files', 'project-files', true);
|
||||
|
||||
-- Enable realtime for comments
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.project_comments;
|
||||
|
||||
-- RLS for project_comments: authenticated users can read all, insert own
|
||||
CREATE POLICY "Authenticated users can read project comments"
|
||||
ON public.project_comments FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert own comments"
|
||||
ON public.project_comments FOR INSERT TO authenticated
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can update own comments"
|
||||
ON public.project_comments FOR UPDATE TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can delete own comments"
|
||||
ON public.project_comments FOR DELETE TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- RLS for project_files: authenticated users can read all, insert own
|
||||
CREATE POLICY "Authenticated users can read project files"
|
||||
ON public.project_files FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can upload files"
|
||||
ON public.project_files FOR INSERT TO authenticated
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can delete own files"
|
||||
ON public.project_files FOR DELETE TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Admin/manager can delete any file
|
||||
CREATE POLICY "Admins can delete any file"
|
||||
ON public.project_files FOR DELETE TO authenticated
|
||||
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
-- Storage policies for project-files bucket
|
||||
CREATE POLICY "Authenticated users can read project files storage"
|
||||
ON storage.objects FOR SELECT TO authenticated
|
||||
USING (bucket_id = 'project-files');
|
||||
|
||||
CREATE POLICY "Authenticated users can upload project files storage"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'project-files');
|
||||
|
||||
CREATE POLICY "Users can delete own project files storage"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (bucket_id = 'project-files');
|
||||
|
||||
-- Update projects RLS: drop old policy, add new ones allowing clients to create and view
|
||||
DROP POLICY IF EXISTS "Staff full access on projects" ON public.projects;
|
||||
|
||||
-- Admins/managers full access
|
||||
CREATE POLICY "Admin manager full access on projects"
|
||||
ON public.projects FOR ALL TO authenticated
|
||||
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'));
|
||||
|
||||
-- All authenticated can read projects
|
||||
CREATE POLICY "Authenticated users can view projects"
|
||||
ON public.projects FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- All authenticated can create projects
|
||||
CREATE POLICY "Authenticated users can create projects"
|
||||
ON public.projects FOR INSERT TO authenticated
|
||||
WITH CHECK (auth.uid() = created_by);
|
||||
|
||||
-- Users can update own projects (but not status to completed - enforced in app)
|
||||
CREATE POLICY "Users can update own projects"
|
||||
ON public.projects FOR UPDATE TO authenticated
|
||||
USING (auth.uid() = created_by);
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
-- Table to store shared links for folders and documents
|
||||
CREATE TABLE public.shared_links (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
share_type TEXT NOT NULL DEFAULT 'folder', -- 'folder' or 'document'
|
||||
folder_name TEXT, -- for folder shares
|
||||
document_id UUID REFERENCES public.documents(id) ON DELETE CASCADE, -- for document shares
|
||||
is_public BOOLEAN NOT NULL DEFAULT false,
|
||||
access_code TEXT NOT NULL,
|
||||
share_token TEXT NOT NULL UNIQUE DEFAULT encode(gen_random_bytes(16), 'hex'),
|
||||
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
expires_at TIMESTAMP WITH TIME ZONE -- optional expiry
|
||||
);
|
||||
|
||||
ALTER TABLE public.shared_links ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff can manage shared links
|
||||
CREATE POLICY "Staff full access on shared_links"
|
||||
ON public.shared_links
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Public read for validating access codes (anon users need this)
|
||||
CREATE POLICY "Anyone can validate shared links"
|
||||
ON public.shared_links
|
||||
FOR SELECT
|
||||
TO anon
|
||||
USING (is_public = true);
|
||||
|
||||
-- Also allow authenticated users to read
|
||||
CREATE POLICY "Authenticated can read shared links"
|
||||
ON public.shared_links
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE TRIGGER update_shared_links_updated_at
|
||||
BEFORE UPDATE ON public.shared_links
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
-- Allow anonymous users to read documents (needed for shared access page)
|
||||
CREATE POLICY "Anon can read documents via shared links"
|
||||
ON public.documents
|
||||
FOR SELECT
|
||||
TO anon
|
||||
USING (true);
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
-- Create client_invoices table to track invoices generated from billable expenses and forms
|
||||
CREATE TABLE public.client_invoices (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
invoice_number TEXT NOT NULL,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id),
|
||||
amount NUMERIC NOT NULL DEFAULT 0,
|
||||
credits NUMERIC NOT NULL DEFAULT 0,
|
||||
total NUMERIC NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'sent',
|
||||
issue_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
due_date DATE,
|
||||
paid_date DATE,
|
||||
source TEXT NOT NULL DEFAULT 'billable_expenses',
|
||||
notes TEXT,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.client_invoices ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff full access
|
||||
CREATE POLICY "Staff full access on client_invoices"
|
||||
ON public.client_invoices
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Create client_invoice_items to store line items
|
||||
CREATE TABLE public.client_invoice_items (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
invoice_id UUID NOT NULL REFERENCES public.client_invoices(id) ON DELETE CASCADE,
|
||||
billable_expense_id UUID REFERENCES public.billable_expenses(id),
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
date DATE,
|
||||
quantity NUMERIC NOT NULL DEFAULT 1,
|
||||
unit_price NUMERIC NOT NULL DEFAULT 0,
|
||||
amount NUMERIC NOT NULL DEFAULT 0,
|
||||
is_credit BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.client_invoice_items ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on client_invoice_items"
|
||||
ON public.client_invoice_items
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
-- Add new roles to app_role enum for committee members
|
||||
ALTER TYPE public.app_role ADD VALUE IF NOT EXISTS 'board_member';
|
||||
ALTER TYPE public.app_role ADD VALUE IF NOT EXISTS 'arc_member';
|
||||
ALTER TYPE public.app_role ADD VALUE IF NOT EXISTS 'fining_member';
|
||||
|
||||
-- Create role_permissions table for feature-area CRUD permissions
|
||||
CREATE TABLE public.role_permissions (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
role TEXT NOT NULL,
|
||||
feature_area TEXT NOT NULL,
|
||||
can_read BOOLEAN NOT NULL DEFAULT false,
|
||||
can_create BOOLEAN NOT NULL DEFAULT false,
|
||||
can_edit BOOLEAN NOT NULL DEFAULT false,
|
||||
can_delete BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
UNIQUE(role, feature_area)
|
||||
);
|
||||
|
||||
ALTER TABLE public.role_permissions ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Only admins can manage permissions
|
||||
CREATE POLICY "Admins can manage role_permissions"
|
||||
ON public.role_permissions
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (public.has_role(auth.uid(), 'admin'::app_role))
|
||||
WITH CHECK (public.has_role(auth.uid(), 'admin'::app_role));
|
||||
|
||||
-- All authenticated can read permissions (needed to enforce them client-side)
|
||||
CREATE POLICY "Authenticated can read role_permissions"
|
||||
ON public.role_permissions
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- Seed default permissions for all roles and feature areas
|
||||
INSERT INTO public.role_permissions (role, feature_area, can_read, can_create, can_edit, can_delete)
|
||||
SELECT r.role, f.feature_area,
|
||||
CASE WHEN r.role = 'homeowner' THEN
|
||||
f.feature_area IN ('Owner Portal', 'Documents', 'Announcements', 'Calendar')
|
||||
ELSE true END,
|
||||
CASE WHEN r.role = 'homeowner' THEN false
|
||||
WHEN r.role IN ('board_member','arc_member','fining_member') THEN false
|
||||
ELSE true END,
|
||||
CASE WHEN r.role = 'homeowner' THEN false
|
||||
WHEN r.role IN ('board_member','arc_member','fining_member') THEN false
|
||||
ELSE true END,
|
||||
CASE WHEN r.role IN ('admin') THEN true ELSE false END
|
||||
FROM
|
||||
(VALUES ('admin'),('manager'),('staff'),('employee'),('homeowner'),('board_member'),('arc_member'),('fining_member')) AS r(role),
|
||||
(VALUES ('Financial'),('Owners & Units'),('Violations'),('ARC Applications'),('Collections'),('Documents'),('Announcements'),('Calendar'),('Vendors & Bills'),('Projects'),('Email & Notifications'),('Legal Matters'),('Inspections'),('Owner Portal'),('Board Votes'),('Estoppels'),('Parking')) AS f(feature_area)
|
||||
ON CONFLICT (role, feature_area) DO NOTHING;
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE TRIGGER update_role_permissions_updated_at
|
||||
BEFORE UPDATE ON public.role_permissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
-- Add target_month (YYYY-MM-01 format) and completed_at to payables
|
||||
ALTER TABLE public.payables ADD COLUMN target_month DATE DEFAULT date_trunc('month', CURRENT_DATE)::date;
|
||||
ALTER TABLE public.payables ADD COLUMN completed_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
-- Update existing payables to have target_month based on due_date or created_at
|
||||
UPDATE public.payables SET target_month = COALESCE(date_trunc('month', due_date)::date, date_trunc('month', created_at)::date);
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
-- Store validation proof records for forms & letters
|
||||
CREATE TABLE public.document_validation_proofs (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
document_type TEXT NOT NULL,
|
||||
document_title TEXT,
|
||||
generated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
generated_by UUID,
|
||||
association_id UUID REFERENCES public.associations(id),
|
||||
metadata JSONB DEFAULT '{}',
|
||||
verification_hash TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.document_validation_proofs ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on document_validation_proofs"
|
||||
ON public.document_validation_proofs
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (public.has_role(auth.uid(), 'admin'::app_role) OR public.has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (public.has_role(auth.uid(), 'admin'::app_role) OR public.has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
-- Anyone can verify a proof by ID (public read for verification)
|
||||
CREATE POLICY "Anyone can verify proofs"
|
||||
ON public.document_validation_proofs
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (true);
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
-- ARC application votes
|
||||
CREATE TABLE public.arc_application_votes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
application_id UUID NOT NULL REFERENCES public.arc_applications(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
vote TEXT NOT NULL CHECK (vote IN ('approve', 'deny')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (application_id, user_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.arc_application_votes ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on arc_application_votes"
|
||||
ON public.arc_application_votes FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
CREATE POLICY "Authenticated users can view arc_application_votes"
|
||||
ON public.arc_application_votes FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- ARC application comments
|
||||
CREATE TABLE public.arc_application_comments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
application_id UUID NOT NULL REFERENCES public.arc_applications(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
comment TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.arc_application_comments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on arc_application_comments"
|
||||
ON public.arc_application_comments FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
CREATE POLICY "Authenticated users can view arc_application_comments"
|
||||
ON public.arc_application_comments FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
-- Generic entity votes (reusable for bids, board votes, etc.)
|
||||
CREATE TABLE public.entity_votes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
vote TEXT NOT NULL CHECK (vote IN ('approve', 'deny')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (entity_type, entity_id, user_id)
|
||||
);
|
||||
CREATE INDEX idx_entity_votes_lookup ON public.entity_votes(entity_type, entity_id);
|
||||
ALTER TABLE public.entity_votes ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on entity_votes"
|
||||
ON public.entity_votes FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
CREATE POLICY "Authenticated users can view entity_votes"
|
||||
ON public.entity_votes FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- Generic entity comments
|
||||
CREATE TABLE public.entity_comments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
comment TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX idx_entity_comments_lookup ON public.entity_comments(entity_type, entity_id);
|
||||
ALTER TABLE public.entity_comments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on entity_comments"
|
||||
ON public.entity_comments FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
CREATE POLICY "Authenticated users can view entity_comments"
|
||||
ON public.entity_comments FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
-- Board votes table for the Board Votes feature
|
||||
CREATE TABLE public.board_votes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.board_votes ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff full access on board_votes"
|
||||
ON public.board_votes FOR ALL TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role));
|
||||
|
||||
CREATE POLICY "Authenticated users can view board_votes"
|
||||
ON public.board_votes FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
-- Add new columns to legal_matters to match the reference design
|
||||
ALTER TABLE public.legal_matters
|
||||
ADD COLUMN IF NOT EXISTS case_type TEXT DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS official_case_name TEXT DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS current_stage TEXT DEFAULT 'intent_to_lien',
|
||||
ADD COLUMN IF NOT EXISTS judge TEXT DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS amount_due NUMERIC DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS opposing_counsel TEXT DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS unit_id UUID REFERENCES public.units(id) DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS collection_id UUID REFERENCES public.collections(id) DEFAULT NULL;
|
||||
|
||||
-- Create a DB function that auto-creates a legal matter when a collection is inserted
|
||||
CREATE OR REPLACE FUNCTION public.auto_create_legal_matter_from_collection()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO 'public'
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.legal_matters (
|
||||
association_id,
|
||||
title,
|
||||
case_type,
|
||||
category,
|
||||
current_stage,
|
||||
amount_due,
|
||||
collection_id,
|
||||
status,
|
||||
description
|
||||
) VALUES (
|
||||
NEW.association_id,
|
||||
'Collection Case - ' || COALESCE(NEW.notes, 'No description'),
|
||||
'collection',
|
||||
'collections',
|
||||
'intent_to_lien',
|
||||
NEW.amount_owed,
|
||||
NEW.id,
|
||||
'open',
|
||||
NEW.notes
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Create the trigger
|
||||
CREATE TRIGGER trg_collection_to_legal_matter
|
||||
AFTER INSERT ON public.collections
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.auto_create_legal_matter_from_collection();
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
-- Create parking_records table for violation tracking
|
||||
CREATE TABLE public.parking_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id),
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
description TEXT,
|
||||
address TEXT,
|
||||
vehicle_plate TEXT,
|
||||
vehicle_make TEXT,
|
||||
vehicle_model TEXT,
|
||||
photo_url TEXT,
|
||||
warning_level TEXT NOT NULL DEFAULT 'first_warning' CHECK (warning_level IN ('first_warning', 'second_warning', 'final_warning')),
|
||||
recorded_by TEXT,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
citation TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'closed', 'resolved')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.parking_records ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view parking records"
|
||||
ON public.parking_records FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Admin/manager can insert parking records"
|
||||
ON public.parking_records FOR INSERT TO authenticated
|
||||
WITH CHECK (
|
||||
public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager')
|
||||
);
|
||||
|
||||
CREATE POLICY "Admin/manager can update parking records"
|
||||
ON public.parking_records FOR UPDATE TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager')
|
||||
);
|
||||
|
||||
CREATE POLICY "Admin/manager can delete parking records"
|
||||
ON public.parking_records FOR DELETE TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager')
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_parking_records_updated_at
|
||||
BEFORE UPDATE ON public.parking_records
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
-- Create parking-photos storage bucket
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('parking-photos', 'parking-photos', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
CREATE POLICY "Anyone can view parking photos"
|
||||
ON storage.objects FOR SELECT USING (bucket_id = 'parking-photos');
|
||||
|
||||
CREATE POLICY "Authenticated can upload parking photos"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'parking-photos');
|
||||
|
||||
CREATE POLICY "Authenticated can update parking photos"
|
||||
ON storage.objects FOR UPDATE TO authenticated
|
||||
USING (bucket_id = 'parking-photos');
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
ALTER TABLE public.owners
|
||||
ADD COLUMN IF NOT EXISTS street_address TEXT DEFAULT NULL;
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
-- Public form templates (designed by admins)
|
||||
CREATE TABLE public.public_form_templates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
fields JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
settings JSONB DEFAULT '{}'::jsonb,
|
||||
is_published BOOLEAN DEFAULT false,
|
||||
slug TEXT UNIQUE,
|
||||
require_auth BOOLEAN DEFAULT false,
|
||||
allow_attachments BOOLEAN DEFAULT true,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Public form submissions
|
||||
CREATE TABLE public.public_form_submissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
template_id UUID NOT NULL REFERENCES public.public_form_templates(id) ON DELETE CASCADE,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
submitter_name TEXT,
|
||||
submitter_email TEXT,
|
||||
form_data JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
attachments JSONB DEFAULT '[]'::jsonb,
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.public_form_templates ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.public_form_submissions ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff can manage templates
|
||||
CREATE POLICY "Staff can manage form templates"
|
||||
ON public.public_form_templates FOR ALL TO authenticated
|
||||
USING (true) WITH CHECK (true);
|
||||
|
||||
-- Staff can view submissions
|
||||
CREATE POLICY "Staff can manage form submissions"
|
||||
ON public.public_form_submissions FOR ALL TO authenticated
|
||||
USING (true) WITH CHECK (true);
|
||||
|
||||
-- Anonymous users can view published templates
|
||||
CREATE POLICY "Anyone can view published templates"
|
||||
ON public.public_form_templates FOR SELECT TO anon
|
||||
USING (is_published = true);
|
||||
|
||||
-- Anonymous users can submit forms
|
||||
CREATE POLICY "Anyone can submit forms"
|
||||
ON public.public_form_submissions FOR INSERT TO anon
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Triggers
|
||||
CREATE TRIGGER update_public_form_templates_updated_at
|
||||
BEFORE UPDATE ON public.public_form_templates
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_public_form_submissions_updated_at
|
||||
BEFORE UPDATE ON public.public_form_submissions
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
-- Storage bucket for public form attachments
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('public-form-attachments', 'public-form-attachments', true);
|
||||
|
||||
CREATE POLICY "Anyone can upload form attachments"
|
||||
ON storage.objects FOR INSERT TO anon
|
||||
WITH CHECK (bucket_id = 'public-form-attachments');
|
||||
|
||||
CREATE POLICY "Anyone can view form attachments"
|
||||
ON storage.objects FOR SELECT TO anon
|
||||
USING (bucket_id = 'public-form-attachments');
|
||||
|
||||
CREATE POLICY "Auth users manage form attachments"
|
||||
ON storage.objects FOR ALL TO authenticated
|
||||
USING (bucket_id = 'public-form-attachments');
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
-- Fee schedule subcategories
|
||||
CREATE TABLE public.fee_schedule_subcategories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
is_custom BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Fee schedules
|
||||
CREATE TABLE public.fee_schedules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
description TEXT NOT NULL,
|
||||
fee NUMERIC NOT NULL DEFAULT 0,
|
||||
category TEXT,
|
||||
subcategory TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.fee_schedule_subcategories ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.fee_schedules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can manage subcategories"
|
||||
ON public.fee_schedule_subcategories FOR ALL TO authenticated
|
||||
USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can manage fee schedules"
|
||||
ON public.fee_schedules FOR ALL TO authenticated
|
||||
USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE TRIGGER update_fee_schedules_updated_at
|
||||
BEFORE UPDATE ON public.fee_schedules
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
-- Seed default subcategories
|
||||
INSERT INTO public.fee_schedule_subcategories (name, is_custom) VALUES
|
||||
('Estoppel', false),
|
||||
('Late Fee', false),
|
||||
('Legal', false),
|
||||
('Compliance', false),
|
||||
('Administrative', false),
|
||||
('Maintenance', false);
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
-- Add attorney and CPA fields to associations
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS attorney_name TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS attorney_firm TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS attorney_email TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS attorney_phone TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS cpa_name TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS cpa_firm TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS cpa_email TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS cpa_phone TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS insurance_provider TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS insurance_expiration DATE;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS website TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS care_of_address TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS mailing_address TEXT;
|
||||
ALTER TABLE public.associations ADD COLUMN IF NOT EXISTS contact_email TEXT;
|
||||
|
||||
-- Violation types per association
|
||||
CREATE TABLE IF NOT EXISTS public.violation_types (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
category TEXT NOT NULL,
|
||||
article_section TEXT,
|
||||
citation_text TEXT,
|
||||
requested_action TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.violation_types ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can manage violation types"
|
||||
ON public.violation_types FOR ALL TO authenticated
|
||||
USING (true) WITH CHECK (true);
|
||||
|
||||
CREATE TRIGGER update_violation_types_updated_at
|
||||
BEFORE UPDATE ON public.violation_types
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
-- 1. Create public_form_submission_reports table
|
||||
CREATE TABLE public.public_form_submission_reports (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
submission_id UUID NOT NULL REFERENCES public.public_form_submissions(id) ON DELETE CASCADE,
|
||||
template_id UUID NOT NULL REFERENCES public.public_form_templates(id) ON DELETE CASCADE,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
report_data JSONB,
|
||||
status TEXT NOT NULL DEFAULT 'generated',
|
||||
generated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.public_form_submission_reports ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff can manage reports
|
||||
CREATE POLICY "Staff can manage submission reports"
|
||||
ON public.public_form_submission_reports
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
-- 2. Add report_styling column to public_form_templates if missing
|
||||
ALTER TABLE public.public_form_templates ADD COLUMN IF NOT EXISTS report_styling JSONB;
|
||||
|
||||
-- 3. Fix shared_links: ensure share_token has a default so INSERT works
|
||||
ALTER TABLE public.shared_links ALTER COLUMN share_token SET DEFAULT encode(gen_random_bytes(16), 'hex');
|
||||
|
||||
-- 4. Fix the documents anon policy - it's too permissive (allows reading ALL documents)
|
||||
-- Replace with a scoped policy that only allows reading documents referenced by a public shared link
|
||||
DROP POLICY IF EXISTS "Anon can read documents via shared links" ON public.documents;
|
||||
|
||||
CREATE POLICY "Anon can read documents via shared links"
|
||||
ON public.documents
|
||||
FOR SELECT
|
||||
TO anon
|
||||
USING (
|
||||
id IN (
|
||||
SELECT sl.document_id FROM public.shared_links sl WHERE sl.is_public = true AND sl.document_id IS NOT NULL
|
||||
)
|
||||
OR
|
||||
category IN (
|
||||
SELECT sl.folder_name FROM public.shared_links sl WHERE sl.is_public = true AND sl.share_type = 'folder'
|
||||
)
|
||||
);
|
||||
|
||||
-- 5. Add employee/staff roles to shared_links policy so non-admin staff can create share links
|
||||
DROP POLICY IF EXISTS "Staff full access on shared_links" ON public.shared_links;
|
||||
|
||||
CREATE POLICY "Staff full access on shared_links"
|
||||
ON public.shared_links
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager') OR has_role(auth.uid(), 'employee') OR has_role(auth.uid(), 'staff'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager') OR has_role(auth.uid(), 'employee') OR has_role(auth.uid(), 'staff'));
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE TRIGGER update_public_form_submission_reports_updated_at
|
||||
BEFORE UPDATE ON public.public_form_submission_reports
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,96 @@
|
||||
|
||||
-- Stripe account mappings per association
|
||||
CREATE TABLE public.stripe_account_mappings (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
stripe_account_id TEXT NOT NULL,
|
||||
stripe_public_key TEXT NOT NULL,
|
||||
stripe_secret_key TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
pass_processing_fee BOOLEAN NOT NULL DEFAULT false,
|
||||
processing_fee_percent NUMERIC(5,4) NOT NULL DEFAULT 0.029,
|
||||
processing_fee_fixed_cents INTEGER NOT NULL DEFAULT 30,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
UNIQUE(association_id),
|
||||
UNIQUE(stripe_account_id)
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.stripe_account_mappings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Admin/manager full access
|
||||
CREATE POLICY "Staff can manage stripe mappings"
|
||||
ON public.stripe_account_mappings
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager'));
|
||||
|
||||
-- Homeowners can read active mappings (to get public key for their association)
|
||||
CREATE POLICY "Homeowners can read active stripe mappings"
|
||||
ON public.stripe_account_mappings
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (is_active = true);
|
||||
|
||||
-- Stripe payment records
|
||||
CREATE TABLE public.stripe_payments (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
||||
stripe_payment_intent_id TEXT,
|
||||
amount_cents INTEGER NOT NULL,
|
||||
fee_cents INTEGER NOT NULL DEFAULT 0,
|
||||
total_cents INTEGER NOT NULL,
|
||||
payment_method_type TEXT NOT NULL DEFAULT 'card',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
description TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.stripe_payments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff full access
|
||||
CREATE POLICY "Staff can manage stripe payments"
|
||||
ON public.stripe_payments
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager') OR has_role(auth.uid(), 'employee') OR has_role(auth.uid(), 'staff'))
|
||||
WITH CHECK (has_role(auth.uid(), 'admin') OR has_role(auth.uid(), 'manager') OR has_role(auth.uid(), 'employee') OR has_role(auth.uid(), 'staff'));
|
||||
|
||||
-- Homeowners can see their own payments
|
||||
CREATE POLICY "Homeowners can view own stripe payments"
|
||||
ON public.stripe_payments
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
owner_id IN (
|
||||
SELECT o.id FROM public.owners o WHERE o.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Homeowners can insert their own payments
|
||||
CREATE POLICY "Homeowners can create own stripe payments"
|
||||
ON public.stripe_payments
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (
|
||||
owner_id IN (
|
||||
SELECT o.id FROM public.owners o WHERE o.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Triggers
|
||||
CREATE TRIGGER update_stripe_account_mappings_updated_at
|
||||
BEFORE UPDATE ON public.stripe_account_mappings
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_stripe_payments_updated_at
|
||||
BEFORE UPDATE ON public.stripe_payments
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
-- Homeowners can read documents belonging to their association
|
||||
CREATE POLICY "Homeowners can view association documents"
|
||||
ON public.documents FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'homeowner'::app_role)
|
||||
AND association_id IN (
|
||||
SELECT o.association_id FROM public.owners o WHERE o.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Homeowners can insert ARC applications for their association
|
||||
CREATE POLICY "Homeowners can submit ARC applications"
|
||||
ON public.arc_applications FOR INSERT TO authenticated
|
||||
WITH CHECK (
|
||||
has_role(auth.uid(), 'homeowner'::app_role)
|
||||
AND association_id IN (
|
||||
SELECT o.association_id FROM public.owners o WHERE o.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Homeowners can view their own ARC applications
|
||||
CREATE POLICY "Homeowners can view own ARC applications"
|
||||
ON public.arc_applications FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'homeowner'::app_role)
|
||||
AND owner_id IN (
|
||||
SELECT o.id FROM public.owners o WHERE o.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Homeowners can update their own pending ARC applications
|
||||
CREATE POLICY "Homeowners can update own pending ARC applications"
|
||||
ON public.arc_applications FOR UPDATE TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'homeowner'::app_role)
|
||||
AND owner_id IN (
|
||||
SELECT o.id FROM public.owners o WHERE o.user_id = auth.uid()
|
||||
)
|
||||
AND status IN ('pending', 'draft')
|
||||
);
|
||||
|
||||
-- Homeowners can add comments to their own ARC applications
|
||||
CREATE POLICY "Homeowners can comment on own ARC apps"
|
||||
ON public.arc_application_comments FOR INSERT TO authenticated
|
||||
WITH CHECK (
|
||||
has_role(auth.uid(), 'homeowner'::app_role)
|
||||
AND application_id IN (
|
||||
SELECT a.id FROM public.arc_applications a
|
||||
JOIN public.owners o ON o.id = a.owner_id
|
||||
WHERE o.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members: add user_id column to board_members for linking
|
||||
ALTER TABLE public.board_members ADD COLUMN IF NOT EXISTS user_id uuid REFERENCES auth.users(id);
|
||||
|
||||
-- Board members get full read access to all documents in their association
|
||||
CREATE POLICY "Board members can view association documents"
|
||||
ON public.documents FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (
|
||||
SELECT bm.association_id FROM public.board_members bm WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can view all ARC applications in their association
|
||||
CREATE POLICY "Board members can view association ARC applications"
|
||||
ON public.arc_applications FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (
|
||||
SELECT bm.association_id FROM public.board_members bm WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can vote on ARC applications
|
||||
CREATE POLICY "Board members can vote on ARC applications"
|
||||
ON public.arc_application_votes FOR INSERT TO authenticated
|
||||
WITH CHECK (
|
||||
application_id IN (
|
||||
SELECT a.id FROM public.arc_applications a
|
||||
JOIN public.board_members bm ON bm.association_id = a.association_id
|
||||
WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can comment on ARC applications
|
||||
CREATE POLICY "Board members can comment on ARC applications"
|
||||
ON public.arc_application_comments FOR INSERT TO authenticated
|
||||
WITH CHECK (
|
||||
application_id IN (
|
||||
SELECT a.id FROM public.arc_applications a
|
||||
JOIN public.board_members bm ON bm.association_id = a.association_id
|
||||
WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can update ARC application status (approve/deny)
|
||||
CREATE POLICY "Board members can update ARC applications"
|
||||
ON public.arc_applications FOR UPDATE TO authenticated
|
||||
USING (
|
||||
association_id IN (
|
||||
SELECT bm.association_id FROM public.board_members bm
|
||||
WHERE bm.user_id = auth.uid() AND bm.approval_authority = true
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
-- Align backend owner-roster access with granular feature permissions while preserving admin/manager access.
|
||||
CREATE OR REPLACE FUNCTION public.has_feature_permission(_user_id uuid, _feature_area text, _action text)
|
||||
RETURNS boolean
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
SELECT
|
||||
public.has_role(_user_id, 'admin'::public.app_role)
|
||||
OR public.has_role(_user_id, 'manager'::public.app_role)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM public.user_roles ur
|
||||
JOIN public.role_permissions rp
|
||||
ON rp.role = ur.role::text
|
||||
WHERE ur.user_id = _user_id
|
||||
AND rp.feature_area = _feature_area
|
||||
AND CASE _action
|
||||
WHEN 'read' THEN rp.can_read
|
||||
WHEN 'create' THEN rp.can_create
|
||||
WHEN 'edit' THEN rp.can_edit
|
||||
WHEN 'delete' THEN rp.can_delete
|
||||
ELSE false
|
||||
END
|
||||
);
|
||||
$$;
|
||||
|
||||
CREATE POLICY "Authorized users can create owners"
|
||||
ON public.owners
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (public.has_feature_permission(auth.uid(), 'Owners & Units', 'create'));
|
||||
|
||||
CREATE POLICY "Authorized users can update owners"
|
||||
ON public.owners
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (public.has_feature_permission(auth.uid(), 'Owners & Units', 'edit'))
|
||||
WITH CHECK (public.has_feature_permission(auth.uid(), 'Owners & Units', 'edit'));
|
||||
|
||||
CREATE POLICY "Authorized users can delete owners"
|
||||
ON public.owners
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (public.has_feature_permission(auth.uid(), 'Owners & Units', 'delete'));
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add "User Management" feature area for board_member with read + create + edit
|
||||
INSERT INTO public.role_permissions (role, feature_area, can_read, can_create, can_edit, can_delete)
|
||||
VALUES ('board_member', 'User Management', true, true, true, false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.collections ADD COLUMN unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL;
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Allow any authenticated user to insert their own comments
|
||||
CREATE POLICY "Authenticated users can insert own entity_comments"
|
||||
ON public.entity_comments
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Allow any authenticated user to insert their own votes
|
||||
CREATE POLICY "Authenticated users can insert own entity_votes"
|
||||
ON public.entity_votes
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Allow any authenticated user to update their own votes
|
||||
CREATE POLICY "Authenticated users can update own entity_votes"
|
||||
ON public.entity_votes
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (auth.uid() = user_id)
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- Allow any authenticated user to delete their own votes
|
||||
CREATE POLICY "Authenticated users can delete own entity_votes"
|
||||
ON public.entity_votes
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Allow any authenticated user to delete their own comments
|
||||
CREATE POLICY "Authenticated users can delete own entity_comments"
|
||||
ON public.entity_comments
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Enable realtime for both tables
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.entity_votes;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.entity_comments;
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('invoices', 'invoices', true) ON CONFLICT (id) DO NOTHING;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.bills ADD COLUMN IF NOT EXISTS zoho_bill_id TEXT;
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
CREATE TABLE public.bill_comments (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
bill_id UUID NOT NULL REFERENCES public.bills(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL,
|
||||
user_name TEXT NOT NULL DEFAULT '',
|
||||
comment TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.bill_comments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can read bill comments"
|
||||
ON public.bill_comments FOR SELECT TO authenticated USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert bill comments"
|
||||
ON public.bill_comments FOR INSERT TO authenticated WITH CHECK (true);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.associations ADD CONSTRAINT associations_name_unique UNIQUE (name);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE public.owners ADD CONSTRAINT owners_association_id_property_address_unique UNIQUE (association_id, property_address);
|
||||
ALTER TABLE public.chart_of_accounts ADD CONSTRAINT chart_of_accounts_association_id_account_number_unique UNIQUE (association_id, account_number);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.associations ADD COLUMN status text NOT NULL DEFAULT 'active';
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Add 'fined' to violations status check
|
||||
ALTER TABLE public.violations DROP CONSTRAINT violations_status_check;
|
||||
ALTER TABLE public.violations ADD CONSTRAINT violations_status_check CHECK (status = ANY (ARRAY['open', 'pending', 'resolved', 'escalated', 'closed', 'fined']));
|
||||
|
||||
-- Create missing associations
|
||||
INSERT INTO public.associations (id, name, status) VALUES
|
||||
('be7cbde1-3bd6-4728-8a92-a43aa0f39d1d', 'Las Palmos Community Association', 'active'),
|
||||
('b07d0163-6897-4908-9dcf-2d8049afaa96', 'Main Street Community Association', 'active')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.owners DROP CONSTRAINT IF EXISTS owners_association_id_property_address_unique;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE public.calendar_events
|
||||
ADD COLUMN IF NOT EXISTS visibility text[] NOT NULL DEFAULT ARRAY['admin']::text[],
|
||||
ADD COLUMN IF NOT EXISTS is_blocked boolean NOT NULL DEFAULT false;
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
-- Add a unique constraint on owners for Buildium sync upserts
|
||||
-- Using association_id + first_name + last_name + property_address to handle multiple owners at same address
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS owners_buildium_sync_key
|
||||
ON public.owners (association_id, first_name, last_name, property_address)
|
||||
WHERE property_address IS NOT NULL;
|
||||
|
||||
-- Add buildium_owner_id column for future direct ID mapping
|
||||
ALTER TABLE public.owners ADD COLUMN IF NOT EXISTS buildium_owner_id text;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS owners_buildium_id_key ON public.owners (buildium_owner_id) WHERE buildium_owner_id IS NOT NULL;
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE public.units ADD COLUMN IF NOT EXISTS buildium_unit_id text;
|
||||
ALTER TABLE public.units ADD COLUMN IF NOT EXISTS buildium_account_number text;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS units_buildium_unit_id_idx ON public.units (buildium_unit_id) WHERE buildium_unit_id IS NOT NULL;
|
||||
@@ -0,0 +1,25 @@
|
||||
-- RLS policies for the 'files' storage bucket
|
||||
CREATE POLICY "Authenticated users can upload to files bucket"
|
||||
ON storage.objects
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (bucket_id = 'files');
|
||||
|
||||
CREATE POLICY "Authenticated users can read files bucket"
|
||||
ON storage.objects
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (bucket_id = 'files');
|
||||
|
||||
CREATE POLICY "Authenticated users can update files bucket"
|
||||
ON storage.objects
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (bucket_id = 'files')
|
||||
WITH CHECK (bucket_id = 'files');
|
||||
|
||||
CREATE POLICY "Authenticated users can delete from files bucket"
|
||||
ON storage.objects
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (bucket_id = 'files');
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Create the missing company-assets bucket
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('company-assets', 'company-assets', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- RLS policies for company-assets bucket
|
||||
CREATE POLICY "Authenticated users can upload to company-assets"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'company-assets');
|
||||
|
||||
CREATE POLICY "Authenticated users can read company-assets"
|
||||
ON storage.objects FOR SELECT TO authenticated
|
||||
USING (bucket_id = 'company-assets');
|
||||
|
||||
CREATE POLICY "Authenticated users can update company-assets"
|
||||
ON storage.objects FOR UPDATE TO authenticated
|
||||
USING (bucket_id = 'company-assets');
|
||||
|
||||
CREATE POLICY "Authenticated users can delete company-assets"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (bucket_id = 'company-assets');
|
||||
|
||||
-- Allow public read access since bucket is public
|
||||
CREATE POLICY "Public can read company-assets"
|
||||
ON storage.objects FOR SELECT TO anon
|
||||
USING (bucket_id = 'company-assets');
|
||||
@@ -0,0 +1,12 @@
|
||||
-- The documents feature stores public file URLs for the 'files' bucket,
|
||||
-- so the bucket itself must be public for downloads/previews to work.
|
||||
UPDATE storage.buckets
|
||||
SET public = true
|
||||
WHERE id = 'files';
|
||||
|
||||
DROP POLICY IF EXISTS "Public can read files bucket" ON storage.objects;
|
||||
CREATE POLICY "Public can read files bucket"
|
||||
ON storage.objects
|
||||
FOR SELECT
|
||||
TO anon
|
||||
USING (bucket_id = 'files');
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
-- Create arc-files storage bucket
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('arc-files', 'arc-files', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Allow authenticated users to upload
|
||||
CREATE POLICY "Authenticated users can upload arc files"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'arc-files');
|
||||
|
||||
-- Allow authenticated users to read
|
||||
CREATE POLICY "Authenticated users can read arc files"
|
||||
ON storage.objects FOR SELECT TO authenticated
|
||||
USING (bucket_id = 'arc-files');
|
||||
|
||||
-- Allow public read for previews
|
||||
CREATE POLICY "Public can read arc files"
|
||||
ON storage.objects FOR SELECT TO anon
|
||||
USING (bucket_id = 'arc-files');
|
||||
|
||||
-- Allow authenticated users to delete their uploads
|
||||
CREATE POLICY "Authenticated users can delete arc files"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (bucket_id = 'arc-files');
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.fee_schedules ADD COLUMN IF NOT EXISTS account text;
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
CREATE TABLE public.migration_field_mappings (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
mapping_type TEXT NOT NULL CHECK (mapping_type IN ('table', 'column', 'id_value')),
|
||||
source_table TEXT,
|
||||
destination_table TEXT,
|
||||
source_field TEXT,
|
||||
destination_field TEXT,
|
||||
source_value TEXT,
|
||||
destination_value TEXT,
|
||||
description TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.migration_field_mappings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Admins can manage migration mappings"
|
||||
ON public.migration_field_mappings
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (public.has_role(auth.uid(), 'admin'))
|
||||
WITH CHECK (public.has_role(auth.uid(), 'admin'));
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
-- Create avatars storage bucket
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Allow authenticated users to upload their own avatar
|
||||
CREATE POLICY "Users can upload own avatar"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text);
|
||||
|
||||
-- Allow authenticated users to update their own avatar
|
||||
CREATE POLICY "Users can update own avatar"
|
||||
ON storage.objects FOR UPDATE TO authenticated
|
||||
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text);
|
||||
|
||||
-- Allow authenticated users to delete their own avatar
|
||||
CREATE POLICY "Users can delete own avatar"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text);
|
||||
|
||||
-- Allow public read access to avatars
|
||||
CREATE POLICY "Public read access for avatars"
|
||||
ON storage.objects FOR SELECT TO public
|
||||
USING (bucket_id = 'avatars');
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.owners ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'active';
|
||||
@@ -0,0 +1,182 @@
|
||||
|
||||
-- Helper function: returns all association_ids the current user belongs to
|
||||
-- via either the owners table or the board_members table
|
||||
CREATE OR REPLACE FUNCTION public.get_user_association_ids()
|
||||
RETURNS SETOF uuid
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
SELECT DISTINCT association_id FROM (
|
||||
SELECT association_id FROM public.owners WHERE user_id = auth.uid()
|
||||
UNION
|
||||
SELECT association_id FROM public.board_members WHERE user_id = auth.uid()
|
||||
) sub
|
||||
$$;
|
||||
|
||||
-- ============================================================
|
||||
-- ASSOCIATIONS: scope homeowners/board to their own HOAs
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view associations" ON public.associations;
|
||||
|
||||
CREATE POLICY "Staff can view all associations"
|
||||
ON public.associations FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Members can view own associations"
|
||||
ON public.associations FOR SELECT TO authenticated
|
||||
USING (
|
||||
id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- OWNERS: homeowners see only their association's owners
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view owners" ON public.owners;
|
||||
|
||||
CREATE POLICY "Staff can view all owners"
|
||||
ON public.owners FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Members can view own association owners"
|
||||
ON public.owners FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- UNITS: scope to association
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view units" ON public.units;
|
||||
|
||||
CREATE POLICY "Staff can view all units"
|
||||
ON public.units FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Members can view own association units"
|
||||
ON public.units FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- VIOLATIONS: scope authenticated users to their association
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view violations" ON public.violations;
|
||||
|
||||
CREATE POLICY "Staff can view all violations"
|
||||
ON public.violations FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Members can view own association violations"
|
||||
ON public.violations FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- BOARD_MEMBERS: scope to association
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view board_members" ON public.board_members;
|
||||
|
||||
CREATE POLICY "Staff can view all board_members"
|
||||
ON public.board_members FOR SELECT TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Members can view own association board_members"
|
||||
ON public.board_members FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- BOARD_VOTES: scope to association
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view board_votes" ON public.board_votes;
|
||||
|
||||
CREATE POLICY "Members can view own association board_votes"
|
||||
ON public.board_votes FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- ASSOCIATION_FAQS: scope to association
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can view association FAQs" ON public.association_faqs;
|
||||
DROP POLICY IF EXISTS "Authenticated users can update association FAQs" ON public.association_faqs;
|
||||
DROP POLICY IF EXISTS "Authenticated users can delete association FAQs" ON public.association_faqs;
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert association FAQs" ON public.association_faqs;
|
||||
|
||||
CREATE POLICY "Staff can manage association FAQs"
|
||||
ON public.association_faqs FOR ALL TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
)
|
||||
WITH CHECK (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Members can view own association FAQs"
|
||||
ON public.association_faqs FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- CALENDAR_EVENTS: add read access for members (staff already has ALL)
|
||||
-- ============================================================
|
||||
CREATE POLICY "Members can view own association calendar_events"
|
||||
ON public.calendar_events FOR SELECT TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT get_user_association_ids())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- ANNOUNCEMENTS: scope to association (announcements table has no
|
||||
-- association_id, so we keep the existing policy but note this)
|
||||
-- ============================================================
|
||||
-- Announcements don't have association_id, keeping existing behavior
|
||||
|
||||
-- ============================================================
|
||||
-- Also tighten board_members DELETE/UPDATE/INSERT to staff only
|
||||
-- ============================================================
|
||||
DROP POLICY IF EXISTS "Authenticated users can delete board_members" ON public.board_members;
|
||||
DROP POLICY IF EXISTS "Authenticated users can update board_members" ON public.board_members;
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert board_members" ON public.board_members;
|
||||
|
||||
CREATE POLICY "Staff can manage board_members"
|
||||
ON public.board_members FOR ALL TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
)
|
||||
WITH CHECK (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
-- Update documents staff policy to include employee role
|
||||
DROP POLICY IF EXISTS "Staff full access on documents" ON public.documents;
|
||||
|
||||
CREATE POLICY "Staff full access on documents"
|
||||
ON public.documents FOR ALL TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
)
|
||||
WITH CHECK (
|
||||
has_role(auth.uid(), 'admin'::app_role)
|
||||
OR has_role(auth.uid(), 'manager'::app_role)
|
||||
OR has_role(auth.uid(), 'employee'::app_role)
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
-- Create custom_variables table for user-defined template variables
|
||||
CREATE TABLE public.custom_variables (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE NOT NULL,
|
||||
variable_name TEXT NOT NULL,
|
||||
display_label TEXT NOT NULL,
|
||||
default_value TEXT DEFAULT '',
|
||||
description TEXT,
|
||||
category TEXT DEFAULT 'general',
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(association_id, variable_name)
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.custom_variables ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff (admin, manager, employee) full access
|
||||
CREATE POLICY "Staff full access on custom_variables"
|
||||
ON public.custom_variables
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'employee'::public.app_role)
|
||||
)
|
||||
WITH CHECK (
|
||||
public.has_role(auth.uid(), 'admin'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'employee'::public.app_role)
|
||||
);
|
||||
|
||||
-- Board members can read variables for their associations
|
||||
CREATE POLICY "Board members read custom_variables"
|
||||
ON public.custom_variables
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT public.get_user_association_ids())
|
||||
);
|
||||
|
||||
-- Updated_at trigger
|
||||
CREATE TRIGGER update_custom_variables_updated_at
|
||||
BEFORE UPDATE ON public.custom_variables
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.tasks;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.arc_applications;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.homeowner_requests;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.bills;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.board_votes;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.reminders;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.projects;
|
||||
@@ -0,0 +1,22 @@
|
||||
ALTER TABLE public.bill_approvals
|
||||
ADD COLUMN IF NOT EXISTS bill_id UUID;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE constraint_schema = 'public'
|
||||
AND table_name = 'bill_approvals'
|
||||
AND constraint_name = 'bill_approvals_bill_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE public.bill_approvals
|
||||
ADD CONSTRAINT bill_approvals_bill_id_fkey
|
||||
FOREIGN KEY (bill_id)
|
||||
REFERENCES public.bills(id)
|
||||
ON DELETE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bill_approvals_bill_id
|
||||
ON public.bill_approvals (bill_id);
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Allow board members to READ bill approvals for their associations
|
||||
CREATE POLICY "Board members can view bill_approvals"
|
||||
ON public.bill_approvals
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT public.get_user_association_ids())
|
||||
);
|
||||
|
||||
-- Allow board members to UPDATE their own approval status (approve/deny)
|
||||
CREATE POLICY "Board members can update bill_approvals"
|
||||
ON public.bill_approvals
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT public.get_user_association_ids())
|
||||
)
|
||||
WITH CHECK (
|
||||
association_id IN (SELECT public.get_user_association_ids())
|
||||
);
|
||||
@@ -0,0 +1,59 @@
|
||||
DROP POLICY IF EXISTS "Board members can view bill_approvals" ON public.bill_approvals;
|
||||
DROP POLICY IF EXISTS "Board members can update bill_approvals" ON public.bill_approvals;
|
||||
|
||||
CREATE POLICY "Assigned approvers can view bills"
|
||||
ON public.bills
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.bill_approvals ba
|
||||
JOIN public.board_members bm
|
||||
ON bm.association_id = ba.association_id
|
||||
AND bm.member_name = ba.vendor_name
|
||||
WHERE ba.bill_id = public.bills.id
|
||||
AND bm.user_id = auth.uid()
|
||||
AND bm.approval_authority = true
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Assigned approvers can view bill_approvals"
|
||||
ON public.bill_approvals
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.board_members bm
|
||||
WHERE bm.association_id = public.bill_approvals.association_id
|
||||
AND bm.member_name = public.bill_approvals.vendor_name
|
||||
AND bm.user_id = auth.uid()
|
||||
AND bm.approval_authority = true
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Assigned approvers can update bill_approvals"
|
||||
ON public.bill_approvals
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.board_members bm
|
||||
WHERE bm.association_id = public.bill_approvals.association_id
|
||||
AND bm.member_name = public.bill_approvals.vendor_name
|
||||
AND bm.user_id = auth.uid()
|
||||
AND bm.approval_authority = true
|
||||
)
|
||||
)
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.board_members bm
|
||||
WHERE bm.association_id = public.bill_approvals.association_id
|
||||
AND bm.member_name = public.bill_approvals.vendor_name
|
||||
AND bm.user_id = auth.uid()
|
||||
AND bm.approval_authority = true
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,70 @@
|
||||
-- Drop overly restrictive policies that only match by member_name
|
||||
DROP POLICY IF EXISTS "Assigned approvers can view bills" ON public.bills;
|
||||
DROP POLICY IF EXISTS "Assigned approvers can view bill_approvals" ON public.bill_approvals;
|
||||
DROP POLICY IF EXISTS "Assigned approvers can update bill_approvals" ON public.bill_approvals;
|
||||
|
||||
-- Board members can view ALL bills for their association
|
||||
CREATE POLICY "Board members can view association bills"
|
||||
ON public.bills
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
association_id IN (
|
||||
SELECT bm.association_id
|
||||
FROM public.board_members bm
|
||||
WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can view ALL approvals for bills in their association
|
||||
CREATE POLICY "Board members can view association bill_approvals"
|
||||
ON public.bill_approvals
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
association_id IN (
|
||||
SELECT bm.association_id
|
||||
FROM public.board_members bm
|
||||
WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can only update their OWN approval row (matched by member_name)
|
||||
CREATE POLICY "Board members can update own bill_approvals"
|
||||
ON public.bill_approvals
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.board_members bm
|
||||
WHERE bm.association_id = bill_approvals.association_id
|
||||
AND bm.member_name = bill_approvals.vendor_name
|
||||
AND bm.user_id = auth.uid()
|
||||
)
|
||||
)
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.board_members bm
|
||||
WHERE bm.association_id = bill_approvals.association_id
|
||||
AND bm.member_name = bill_approvals.vendor_name
|
||||
AND bm.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Board members can insert comments on bills in their association
|
||||
CREATE POLICY "Board members can insert bill_comments"
|
||||
ON public.bill_comments
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (
|
||||
bill_id IN (
|
||||
SELECT b.id FROM public.bills b
|
||||
WHERE b.association_id IN (
|
||||
SELECT bm.association_id
|
||||
FROM public.board_members bm
|
||||
WHERE bm.user_id = auth.uid()
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
UPDATE public.owners SET user_id = '84f2c9d4-e831-44ad-89a6-513a5515e75f' WHERE id = '29e86467-2f9a-4ea9-b71f-1ee761aa225b' AND user_id IS NULL;
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
-- Direct messages table
|
||||
CREATE TABLE public.direct_messages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
sender_id UUID NOT NULL,
|
||||
recipient_id UUID NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
read_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Index for conversation lookups
|
||||
CREATE INDEX idx_dm_sender ON public.direct_messages(sender_id, created_at DESC);
|
||||
CREATE INDEX idx_dm_recipient ON public.direct_messages(recipient_id, created_at DESC);
|
||||
CREATE INDEX idx_dm_conversation ON public.direct_messages(
|
||||
LEAST(sender_id, recipient_id),
|
||||
GREATEST(sender_id, recipient_id),
|
||||
created_at DESC
|
||||
);
|
||||
|
||||
-- Enable RLS
|
||||
ALTER TABLE public.direct_messages ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Users can read messages they sent or received
|
||||
CREATE POLICY "Users can read own messages"
|
||||
ON public.direct_messages FOR SELECT
|
||||
TO authenticated
|
||||
USING (auth.uid() = sender_id OR auth.uid() = recipient_id);
|
||||
|
||||
-- Users can insert messages where they are the sender
|
||||
CREATE POLICY "Users can send messages"
|
||||
ON public.direct_messages FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (auth.uid() = sender_id);
|
||||
|
||||
-- Users can update messages they received (for marking read)
|
||||
CREATE POLICY "Recipients can mark messages read"
|
||||
ON public.direct_messages FOR UPDATE
|
||||
TO authenticated
|
||||
USING (auth.uid() = recipient_id)
|
||||
WITH CHECK (auth.uid() = recipient_id);
|
||||
|
||||
-- Enable realtime
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.direct_messages;
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
-- Step 1: Add account_number column to units (rename from buildium_account_number)
|
||||
ALTER TABLE public.units RENAME COLUMN buildium_account_number TO account_number;
|
||||
|
||||
-- Step 2: Migrate any account_number data from owners to their linked units (if unit doesn't already have one)
|
||||
UPDATE public.units u
|
||||
SET account_number = o.account_number
|
||||
FROM public.owners o
|
||||
WHERE o.unit_id = u.id
|
||||
AND o.account_number IS NOT NULL
|
||||
AND (u.account_number IS NULL OR u.account_number = '');
|
||||
|
||||
-- Step 3: Drop account_number from owners
|
||||
ALTER TABLE public.owners DROP COLUMN account_number;
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
CREATE TABLE public.custom_ledgers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE SET NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
rows JSONB DEFAULT '[]'::jsonb,
|
||||
settings JSONB DEFAULT '{}'::jsonb,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.custom_ledgers ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can manage custom_ledgers"
|
||||
ON public.custom_ledgers
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (true)
|
||||
WITH CHECK (true);
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
-- Add association_ids array column to chart_of_accounts
|
||||
ALTER TABLE public.chart_of_accounts ADD COLUMN association_ids UUID[] DEFAULT '{}';
|
||||
|
||||
-- Migrate existing association_id values into the array
|
||||
UPDATE public.chart_of_accounts
|
||||
SET association_ids = ARRAY[association_id]
|
||||
WHERE association_id IS NOT NULL AND (association_ids IS NULL OR association_ids = '{}');
|
||||
|
||||
-- Create index for array containment queries
|
||||
CREATE INDEX idx_chart_of_accounts_association_ids ON public.chart_of_accounts USING GIN (association_ids);
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
CREATE TABLE public.support_chats (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL,
|
||||
association_id uuid REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
status text NOT NULL DEFAULT 'open',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE public.support_chat_messages (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
chat_id uuid NOT NULL REFERENCES public.support_chats(id) ON DELETE CASCADE,
|
||||
role text NOT NULL DEFAULT 'user',
|
||||
content text NOT NULL,
|
||||
escalated boolean NOT NULL DEFAULT false,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.support_chats ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.support_chat_messages ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users can manage own support chats" ON public.support_chats
|
||||
FOR ALL TO authenticated USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid());
|
||||
|
||||
CREATE POLICY "Users can read own chat messages" ON public.support_chat_messages
|
||||
FOR SELECT TO authenticated
|
||||
USING (chat_id IN (SELECT id FROM public.support_chats WHERE user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Users can insert own chat messages" ON public.support_chat_messages
|
||||
FOR INSERT TO authenticated
|
||||
WITH CHECK (chat_id IN (SELECT id FROM public.support_chats WHERE user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Admins can read all support chats" ON public.support_chats
|
||||
FOR SELECT TO authenticated USING (public.has_role(auth.uid(), 'admin'));
|
||||
|
||||
CREATE POLICY "Admins can read all chat messages" ON public.support_chat_messages
|
||||
FOR SELECT TO authenticated USING (
|
||||
chat_id IN (SELECT id FROM public.support_chats)
|
||||
AND public.has_role(auth.uid(), 'admin')
|
||||
);
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
CREATE TABLE public.docusign_envelopes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) NOT NULL,
|
||||
envelope_id TEXT,
|
||||
document_name TEXT NOT NULL,
|
||||
document_url TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'created',
|
||||
recipients JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
sent_by UUID REFERENCES auth.users(id),
|
||||
sent_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.docusign_envelopes ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Staff can manage docusign envelopes"
|
||||
ON public.docusign_envelopes
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin'::public.app_role) OR
|
||||
public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
)
|
||||
WITH CHECK (
|
||||
public.has_role(auth.uid(), 'admin'::public.app_role) OR
|
||||
public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_docusign_envelopes_updated_at
|
||||
BEFORE UPDATE ON public.docusign_envelopes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE public.units ADD COLUMN IF NOT EXISTS image_url text;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE public.violations
|
||||
ADD COLUMN IF NOT EXISTS tags text[] NOT NULL DEFAULT ARRAY[]::text[];
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
-- Make association_id nullable so company-level accounts can exist
|
||||
ALTER TABLE public.stripe_account_mappings ALTER COLUMN association_id DROP NOT NULL;
|
||||
|
||||
-- Drop the unique constraint on association_id (it's one-to-one) so we can have nulls
|
||||
ALTER TABLE public.stripe_account_mappings DROP CONSTRAINT IF EXISTS stripe_account_mappings_association_id_fkey;
|
||||
ALTER TABLE public.stripe_account_mappings DROP CONSTRAINT IF EXISTS stripe_account_mappings_association_id_key;
|
||||
|
||||
-- Re-add FK but allow nulls
|
||||
ALTER TABLE public.stripe_account_mappings
|
||||
ADD CONSTRAINT stripe_account_mappings_association_id_fkey
|
||||
FOREIGN KEY (association_id) REFERENCES public.associations(id) ON DELETE CASCADE;
|
||||
|
||||
-- Add unique constraint that allows multiple nulls
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS stripe_account_mappings_association_id_unique
|
||||
ON public.stripe_account_mappings (association_id) WHERE association_id IS NOT NULL;
|
||||
|
||||
-- Add label column for company accounts
|
||||
ALTER TABLE public.stripe_account_mappings ADD COLUMN IF NOT EXISTS label TEXT;
|
||||
@@ -0,0 +1,114 @@
|
||||
|
||||
CREATE TABLE public.form_inbox (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
source_type TEXT NOT NULL,
|
||||
source_id UUID NOT NULL,
|
||||
association_id UUID REFERENCES public.associations(id),
|
||||
title TEXT NOT NULL,
|
||||
submitter_name TEXT,
|
||||
submitter_email TEXT,
|
||||
summary TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'new',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
reviewed_by UUID,
|
||||
reviewed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
ALTER TABLE public.form_inbox ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Admins and managers can view form inbox"
|
||||
ON public.form_inbox FOR SELECT TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin') OR
|
||||
public.has_role(auth.uid(), 'manager')
|
||||
);
|
||||
|
||||
CREATE POLICY "Admins and managers can update form inbox"
|
||||
ON public.form_inbox FOR UPDATE TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin') OR
|
||||
public.has_role(auth.uid(), 'manager')
|
||||
);
|
||||
|
||||
CREATE POLICY "Anyone can insert into form inbox"
|
||||
ON public.form_inbox FOR INSERT
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Trigger to auto-create inbox entry for public form submissions
|
||||
CREATE OR REPLACE FUNCTION public.create_form_inbox_entry_from_submission()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.form_inbox (source_type, source_id, association_id, title, submitter_name, submitter_email, summary)
|
||||
SELECT
|
||||
'public_form',
|
||||
NEW.id,
|
||||
NEW.association_id,
|
||||
COALESCE(t.title, 'Form Submission'),
|
||||
NEW.submitter_name,
|
||||
NEW.submitter_email,
|
||||
t.title
|
||||
FROM public.public_form_templates t
|
||||
WHERE t.id = NEW.template_id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_form_inbox_from_submission
|
||||
AFTER INSERT ON public.public_form_submissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.create_form_inbox_entry_from_submission();
|
||||
|
||||
-- Trigger for violation responses
|
||||
CREATE OR REPLACE FUNCTION public.create_form_inbox_entry_from_violation_response()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_assoc_id UUID;
|
||||
v_title TEXT;
|
||||
BEGIN
|
||||
SELECT v.association_id, 'Violation Response - ' || COALESCE(vt.category, 'Unknown')
|
||||
INTO v_assoc_id, v_title
|
||||
FROM public.violations v
|
||||
LEFT JOIN public.violation_types vt ON v.violation_type_id = vt.id
|
||||
WHERE v.id = NEW.violation_id;
|
||||
|
||||
INSERT INTO public.form_inbox (source_type, source_id, association_id, title, submitter_name, submitter_email, summary)
|
||||
VALUES ('violation_response', NEW.id, v_assoc_id, v_title, NEW.respondent_name, NEW.respondent_email, LEFT(NEW.response_text, 200));
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_form_inbox_from_violation_response
|
||||
AFTER INSERT ON public.violation_responses
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.create_form_inbox_entry_from_violation_response();
|
||||
|
||||
-- Trigger for client requests
|
||||
CREATE OR REPLACE FUNCTION public.create_form_inbox_entry_from_client_request()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.form_inbox (source_type, source_id, association_id, title, submitter_name, submitter_email, summary)
|
||||
VALUES ('client_request', NEW.id, NEW.association_id, NEW.title, NEW.requester_name, NEW.requester_email, LEFT(NEW.description, 200));
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_form_inbox_from_client_request
|
||||
AFTER INSERT ON public.client_requests
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.create_form_inbox_entry_from_client_request();
|
||||
|
||||
-- Enable realtime
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.form_inbox;
|
||||
@@ -0,0 +1,3 @@
|
||||
CREATE POLICY "Anyone can upload violation response photos"
|
||||
ON storage.objects FOR INSERT
|
||||
WITH CHECK (bucket_id = 'violation-photos' AND (storage.foldername(name))[1] = 'responses');
|
||||
@@ -0,0 +1,39 @@
|
||||
CREATE OR REPLACE FUNCTION public.create_form_inbox_entry_from_violation_response()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO 'public'
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_assoc_id UUID;
|
||||
v_title TEXT;
|
||||
BEGIN
|
||||
SELECT
|
||||
v.association_id,
|
||||
'Violation Response - ' || COALESCE(v.category, v.violation_type, v.title, 'Unknown')
|
||||
INTO v_assoc_id, v_title
|
||||
FROM public.violations v
|
||||
WHERE v.id = NEW.violation_id;
|
||||
|
||||
INSERT INTO public.form_inbox (
|
||||
source_type,
|
||||
source_id,
|
||||
association_id,
|
||||
title,
|
||||
submitter_name,
|
||||
submitter_email,
|
||||
summary
|
||||
)
|
||||
VALUES (
|
||||
'violation_response',
|
||||
NEW.id,
|
||||
v_assoc_id,
|
||||
v_title,
|
||||
NEW.respondent_name,
|
||||
NEW.respondent_email,
|
||||
LEFT(COALESCE(NEW.response_text, ''), 200)
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,43 @@
|
||||
CREATE OR REPLACE FUNCTION public.create_form_inbox_entry_from_violation_response()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO 'public'
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_assoc_id UUID;
|
||||
v_title TEXT;
|
||||
v_summary TEXT;
|
||||
v_address TEXT;
|
||||
v_vid_short TEXT;
|
||||
BEGIN
|
||||
SELECT
|
||||
v.association_id,
|
||||
LEFT(v.id::text, 8),
|
||||
COALESCE(v.address, ''),
|
||||
COALESCE(v.category, v.violation_type, v.title, 'Unknown')
|
||||
INTO v_assoc_id, v_vid_short, v_address, v_title
|
||||
FROM public.violations v
|
||||
WHERE v.id = NEW.violation_id;
|
||||
|
||||
v_summary := 'Violation ID: V-' || UPPER(v_vid_short);
|
||||
IF v_address IS NOT NULL AND v_address <> '' THEN
|
||||
v_summary := v_summary || ' | Address: ' || v_address;
|
||||
END IF;
|
||||
IF NEW.response_text IS NOT NULL THEN
|
||||
v_summary := v_summary || ' | ' || LEFT(NEW.response_text, 150);
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.form_inbox (
|
||||
source_type, source_id, association_id, title,
|
||||
submitter_name, submitter_email, summary
|
||||
)
|
||||
VALUES (
|
||||
'violation_response', NEW.id, v_assoc_id,
|
||||
'Violation Response - ' || v_title,
|
||||
NEW.respondent_name, NEW.respondent_email, v_summary
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE POLICY "Admins and managers can delete form inbox"
|
||||
ON public.form_inbox
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (
|
||||
has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role)
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE public.user_dashboard_layouts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
|
||||
layout JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
cards JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
|
||||
ALTER TABLE public.user_dashboard_layouts ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users can manage their own dashboard layout"
|
||||
ON public.user_dashboard_layouts
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (user_id = auth.uid());
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Allow authenticated users (homeowners, board members) to read status updates for their associations
|
||||
CREATE POLICY "Authenticated users can read status updates for their associations"
|
||||
ON public.status_updates
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
association_id IN (SELECT public.get_user_association_ids())
|
||||
OR public.has_role(auth.uid(), 'admin'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'employee'::public.app_role)
|
||||
);
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
-- Store Google Drive OAuth tokens for admin users
|
||||
CREATE TABLE public.google_drive_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL UNIQUE,
|
||||
access_token TEXT NOT NULL,
|
||||
refresh_token TEXT NOT NULL,
|
||||
token_expires_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.google_drive_tokens ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Admins can manage their own tokens"
|
||||
ON public.google_drive_tokens
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
AND (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'))
|
||||
)
|
||||
WITH CHECK (
|
||||
user_id = auth.uid()
|
||||
AND (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'))
|
||||
);
|
||||
|
||||
-- Track which Drive files/folders are shared and with whom
|
||||
CREATE TABLE public.shared_drive_files (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
drive_file_id TEXT NOT NULL,
|
||||
drive_file_name TEXT NOT NULL,
|
||||
drive_mime_type TEXT,
|
||||
drive_icon_link TEXT,
|
||||
drive_web_view_link TEXT,
|
||||
is_folder BOOLEAN NOT NULL DEFAULT false,
|
||||
shared_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
association_ids UUID[] DEFAULT '{}',
|
||||
visibility TEXT[] NOT NULL DEFAULT '{admin}',
|
||||
parent_shared_id UUID REFERENCES public.shared_drive_files(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.shared_drive_files ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Staff can manage shared files
|
||||
CREATE POLICY "Staff can manage shared drive files"
|
||||
ON public.shared_drive_files
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager')
|
||||
)
|
||||
WITH CHECK (
|
||||
public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager')
|
||||
);
|
||||
|
||||
-- Board members and homeowners can view files shared with them
|
||||
CREATE POLICY "Users can view files shared with their role or association"
|
||||
ON public.shared_drive_files
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin')
|
||||
OR public.has_role(auth.uid(), 'manager')
|
||||
OR (
|
||||
'board_member' = ANY(visibility)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.board_members bm
|
||||
WHERE bm.user_id = auth.uid()
|
||||
AND bm.association_id = ANY(shared_drive_files.association_ids)
|
||||
)
|
||||
)
|
||||
OR (
|
||||
'homeowner' = ANY(visibility)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.owners o
|
||||
WHERE o.user_id = auth.uid()
|
||||
AND o.association_id = ANY(shared_drive_files.association_ids)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_google_drive_tokens_updated_at
|
||||
BEFORE UPDATE ON public.google_drive_tokens
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_shared_drive_files_updated_at
|
||||
BEFORE UPDATE ON public.shared_drive_files
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
CREATE TABLE public.forte_account_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
association_id UUID REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
organization_id TEXT NOT NULL,
|
||||
location_id TEXT NOT NULL,
|
||||
api_access_id TEXT NOT NULL,
|
||||
api_secure_key TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
pass_processing_fee BOOLEAN NOT NULL DEFAULT false,
|
||||
processing_fee_percent NUMERIC NOT NULL DEFAULT 0.029,
|
||||
processing_fee_fixed_cents INTEGER NOT NULL DEFAULT 30,
|
||||
label TEXT,
|
||||
environment TEXT NOT NULL DEFAULT 'sandbox',
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.forte_account_mappings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Authenticated users can view forte mappings"
|
||||
ON public.forte_account_mappings FOR SELECT TO authenticated
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Admins can manage forte mappings"
|
||||
ON public.forte_account_mappings FOR ALL TO authenticated
|
||||
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'))
|
||||
WITH CHECK (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user