From 46113a643055401f1ce2b0be30b23b95d201e45d Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 22 Aug 2026 23:11:00 +0000 Subject: [PATCH] Terminal --- .../20260807000900_tasks_and_calendar.sql | 215 ------------------ 1 file changed, 215 deletions(-) delete mode 100644 supabase/migrations/20260807000900_tasks_and_calendar.sql diff --git a/supabase/migrations/20260807000900_tasks_and_calendar.sql b/supabase/migrations/20260807000900_tasks_and_calendar.sql deleted file mode 100644 index d4e10d4..0000000 --- a/supabase/migrations/20260807000900_tasks_and_calendar.sql +++ /dev/null @@ -1,215 +0,0 @@ --- Calendars, reminders and task management — spec section 13. --- --- Section 3 requires Management to have "a separate dashboard and --- administrative calendar with reminders, deadlines, follow-ups, and --- campus-wide tasks". The existing calendar_events table is a single --- org-wide list readable by everyone, so it is extended with a campus, an --- event type and an audience rather than replaced. - --- ============================================================================ --- 1. CALENDAR EXTENSIONS --- ============================================================================ - -ALTER TABLE public.calendar_events - ADD COLUMN IF NOT EXISTS campus_id UUID REFERENCES public.campuses(id) ON DELETE CASCADE, - ADD COLUMN IF NOT EXISTS event_type TEXT NOT NULL DEFAULT 'general', - ADD COLUMN IF NOT EXISTS is_admin_only BOOLEAN NOT NULL DEFAULT FALSE, - ADD COLUMN IF NOT EXISTS visible_to_roles app_role[] NOT NULL DEFAULT '{}', - ADD COLUMN IF NOT EXISTS all_day BOOLEAN NOT NULL DEFAULT TRUE, - ADD COLUMN IF NOT EXISTS start_time TIME, - ADD COLUMN IF NOT EXISTS end_time TIME; - -DO $$ BEGIN - ALTER TABLE public.calendar_events ADD CONSTRAINT cal_event_type_valid - CHECK (event_type IN ('general','holiday','closure','deadline','meeting', - 'professional_development','billing','enrollment','other')); -EXCEPTION WHEN duplicate_object THEN NULL; END $$; - -CREATE INDEX IF NOT EXISTS cal_campus_idx ON public.calendar_events (campus_id); -CREATE INDEX IF NOT EXISTS cal_date_idx ON public.calendar_events (date); -CREATE INDEX IF NOT EXISTS cal_admin_idx ON public.calendar_events (date) WHERE is_admin_only; - --- The original policy published every event to every authenticated user, which --- would leak the new administrative calendar. Replace it with an --- audience-aware read; the admin manage policy from the first migration is --- left untouched. -DROP POLICY IF EXISTS "calendar read" ON public.calendar_events; -CREATE POLICY "calendar read" ON public.calendar_events FOR SELECT TO authenticated - USING ( - public.is_org_admin() - OR ( - NOT is_admin_only - AND (cardinality(visible_to_roles) = 0 OR public.current_user_has_any_role(visible_to_roles)) - AND (campus_id IS NULL OR public.has_campus_access(campus_id) OR public.current_user_has_role('parent')) - ) - OR (is_admin_only AND public.is_management()) - ); - --- ============================================================================ --- 2. TASKS --- ============================================================================ - -CREATE TABLE IF NOT EXISTS public.tasks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - title TEXT NOT NULL, - description TEXT, - - assigned_to UUID REFERENCES auth.users(id) ON DELETE SET NULL, - -- NULL campus means an organization-wide task. - campus_id UUID REFERENCES public.campuses(id) ON DELETE CASCADE, - - due_date DATE, - priority TEXT NOT NULL DEFAULT 'normal', - status TEXT NOT NULL DEFAULT 'open', - - -- What this task is about, when it is about something. - related_student_id UUID REFERENCES public.students(id) ON DELETE CASCADE, - related_invoice_id UUID REFERENCES public.invoices(id) ON DELETE SET NULL, - related_note_id UUID REFERENCES public.student_notes(id) ON DELETE SET NULL, - related_applicant_id UUID REFERENCES public.applicants(id) ON DELETE SET NULL, - - visible_to_roles app_role[] NOT NULL DEFAULT '{}', - - completed_at TIMESTAMPTZ, - completed_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, - - 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(), - - CONSTRAINT task_priority_valid CHECK (priority IN ('low','normal','high','urgent')), - CONSTRAINT task_status_valid CHECK (status IN ('open','in_progress','blocked','done','cancelled')) -); - -GRANT SELECT, INSERT, UPDATE, DELETE ON public.tasks TO authenticated; -GRANT ALL ON public.tasks TO service_role; -ALTER TABLE public.tasks ENABLE ROW LEVEL SECURITY; - -CREATE INDEX IF NOT EXISTS task_assigned_idx ON public.tasks (assigned_to, due_date); -CREATE INDEX IF NOT EXISTS task_campus_idx ON public.tasks (campus_id); -CREATE INDEX IF NOT EXISTS task_student_idx ON public.tasks (related_student_id); -CREATE INDEX IF NOT EXISTS task_invoice_idx ON public.tasks (related_invoice_id); -CREATE INDEX IF NOT EXISTS task_note_idx ON public.tasks (related_note_id); -CREATE INDEX IF NOT EXISTS task_applicant_idx ON public.tasks (related_applicant_id); -CREATE INDEX IF NOT EXISTS task_created_by_idx ON public.tasks (created_by); --- The management dashboard's "what is outstanding" query. -CREATE INDEX IF NOT EXISTS task_open_idx ON public.tasks (due_date) - WHERE status IN ('open','in_progress','blocked'); - -DROP TRIGGER IF EXISTS trg_task_upd ON public.tasks; -CREATE TRIGGER trg_task_upd BEFORE UPDATE ON public.tasks - FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); - --- Stamp completion rather than trusting the client to send both fields. -CREATE OR REPLACE FUNCTION public.stamp_task_completion() -RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ -BEGIN - IF NEW.status = 'done' AND (OLD.status IS DISTINCT FROM 'done') THEN - NEW.completed_at := now(); - NEW.completed_by := (SELECT auth.uid()); - ELSIF NEW.status <> 'done' THEN - NEW.completed_at := NULL; - NEW.completed_by := NULL; - END IF; - RETURN NEW; -END; -$$; - -DROP TRIGGER IF EXISTS trg_task_completion ON public.tasks; -CREATE TRIGGER trg_task_completion BEFORE UPDATE ON public.tasks - FOR EACH ROW EXECUTE FUNCTION public.stamp_task_completion(); - --- ============================================================================ --- 3. REMINDERS --- ============================================================================ --- Delivery is left to the application: a worker polls for due, unsent rows and --- marks them sent. Keeping that state here means a reminder survives restarts --- and can be audited. - -CREATE TABLE IF NOT EXISTS public.reminders ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - body TEXT, - - remind_at TIMESTAMPTZ NOT NULL, - channel TEXT NOT NULL DEFAULT 'in_app', - - task_id UUID REFERENCES public.tasks(id) ON DELETE CASCADE, - related_student_id UUID REFERENCES public.students(id) ON DELETE CASCADE, - related_invoice_id UUID REFERENCES public.invoices(id) ON DELETE SET NULL, - related_certification_id UUID REFERENCES public.staff_certifications(id) ON DELETE CASCADE, - - sent_at TIMESTAMPTZ, - dismissed_at TIMESTAMPTZ, - created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - - CONSTRAINT rem_channel_valid CHECK (channel IN ('in_app','email','sms')) -); - -GRANT SELECT, INSERT, UPDATE, DELETE ON public.reminders TO authenticated; -GRANT ALL ON public.reminders TO service_role; -ALTER TABLE public.reminders ENABLE ROW LEVEL SECURITY; - -CREATE INDEX IF NOT EXISTS rem_user_idx ON public.reminders (user_id, remind_at); -CREATE INDEX IF NOT EXISTS rem_task_idx ON public.reminders (task_id); -CREATE INDEX IF NOT EXISTS rem_student_idx ON public.reminders (related_student_id); -CREATE INDEX IF NOT EXISTS rem_invoice_idx ON public.reminders (related_invoice_id); -CREATE INDEX IF NOT EXISTS rem_cert_idx ON public.reminders (related_certification_id); --- The delivery worker's query: due and not yet sent. -CREATE INDEX IF NOT EXISTS rem_pending_idx ON public.reminders (remind_at) WHERE sent_at IS NULL; - --- ============================================================================ --- 4. POLICIES --- ============================================================================ - --- A task is visible to its assignee, its author, management, and campus --- administrators of the task's campus. -DROP POLICY IF EXISTS "tasks read" ON public.tasks; -CREATE POLICY "tasks read" ON public.tasks FOR SELECT TO authenticated - USING ( - public.is_management() OR public.is_auditor() - OR assigned_to = (SELECT auth.uid()) - OR created_by = (SELECT auth.uid()) - OR (cardinality(visible_to_roles) > 0 AND public.current_user_has_any_role(visible_to_roles)) - OR (campus_id IS NOT NULL - AND public.current_user_has_any_role(ARRAY['campus_admin']::app_role[]) - AND campus_id IN (SELECT public.user_campus_ids())) - ); - -DROP POLICY IF EXISTS "tasks insert" ON public.tasks; -CREATE POLICY "tasks insert" ON public.tasks FOR INSERT TO authenticated - WITH CHECK (created_by = (SELECT auth.uid())); - --- Assignees may progress their own work; management and the author may edit --- anything about it. -DROP POLICY IF EXISTS "tasks update" ON public.tasks; -CREATE POLICY "tasks update" ON public.tasks FOR UPDATE TO authenticated - USING (public.is_management() OR created_by = (SELECT auth.uid()) - OR assigned_to = (SELECT auth.uid())) - WITH CHECK (public.is_management() OR created_by = (SELECT auth.uid()) - OR assigned_to = (SELECT auth.uid())); - -DROP POLICY IF EXISTS "tasks delete" ON public.tasks; -CREATE POLICY "tasks delete" ON public.tasks FOR DELETE TO authenticated - USING (public.is_management() OR created_by = (SELECT auth.uid())); - --- Reminders are personal. Management may create them for others (deadline --- assignment) but may not read another user's reminder list. -DROP POLICY IF EXISTS "reminders read" ON public.reminders; -CREATE POLICY "reminders read" ON public.reminders FOR SELECT TO authenticated - USING (user_id = (SELECT auth.uid()) OR created_by = (SELECT auth.uid())); - -DROP POLICY IF EXISTS "reminders insert" ON public.reminders; -CREATE POLICY "reminders insert" ON public.reminders FOR INSERT TO authenticated - WITH CHECK (user_id = (SELECT auth.uid()) OR public.is_management()); - -DROP POLICY IF EXISTS "reminders update" ON public.reminders; -CREATE POLICY "reminders update" ON public.reminders FOR UPDATE TO authenticated - USING (user_id = (SELECT auth.uid()) OR created_by = (SELECT auth.uid())) - WITH CHECK (user_id = (SELECT auth.uid()) OR created_by = (SELECT auth.uid())); - -DROP POLICY IF EXISTS "reminders delete" ON public.reminders; -CREATE POLICY "reminders delete" ON public.reminders FOR DELETE TO authenticated - USING (user_id = (SELECT auth.uid()) OR created_by = (SELECT auth.uid()));