Files
info-share-spot/supabase/migrations/20260719204512_gradebook.sql
T
adminandClaude Opus 4.8 8fe6ad00f4 Gradebook: classes, weighted grading, assignments, grades, student role
- Migration: student role, students.user_id, grade_categories/grade_scale
  (school defaults + per-class override), assignments, grades + RLS
- Classes area (nav): list/create classes, class page with Gradebook / Roster /
  Grading setup tabs; teachers see their classes, admins all
- Weighted-category grade calc -> letter scale (shared helper)
- Roster assignment (admin), assignment + grade entry (teacher/admin)
- Student profile: view-only Grades tab (parents/students)
- Portal access: create parent OR student logins linked to the student

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:57:27 -04:00

91 lines
5.6 KiB
PL/PgSQL

-- Gradebook: weighted-category grading, per-class assignments & grades, student role.
-- (Applied to the remote via the Supabase MCP; kept here as the source of record.)
ALTER TYPE public.app_role ADD VALUE IF NOT EXISTS 'student';
ALTER TABLE public.students ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_students_user ON public.students(user_id);
CREATE OR REPLACE FUNCTION public.teaches_class(_class UUID)
RETURNS BOOLEAN LANGUAGE SQL STABLE SECURITY DEFINER SET search_path = public AS $$
SELECT EXISTS (SELECT 1 FROM public.classes c WHERE c.id = _class AND c.teacher_id = auth.uid())
$$;
CREATE OR REPLACE FUNCTION public.is_student_account(_student UUID)
RETURNS BOOLEAN LANGUAGE SQL STABLE SECURITY DEFINER SET search_path = public AS $$
SELECT EXISTS (SELECT 1 FROM public.students s WHERE s.id = _student AND s.user_id = auth.uid())
$$;
CREATE TABLE IF NOT EXISTS public.grade_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
class_id UUID REFERENCES public.classes(id) ON DELETE CASCADE,
name TEXT NOT NULL, weight NUMERIC NOT NULL DEFAULT 0, sort_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
ALTER TABLE public.grade_categories ENABLE ROW LEVEL SECURITY;
CREATE TABLE IF NOT EXISTS public.grade_scale (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
class_id UUID REFERENCES public.classes(id) ON DELETE CASCADE,
letter TEXT NOT NULL, min_percent NUMERIC NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
ALTER TABLE public.grade_scale ENABLE ROW LEVEL SECURITY;
CREATE TABLE IF NOT EXISTS public.assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
class_id UUID NOT NULL REFERENCES public.classes(id) ON DELETE CASCADE,
category_id UUID REFERENCES public.grade_categories(id) ON DELETE SET NULL,
name TEXT NOT NULL, max_points NUMERIC NOT NULL DEFAULT 100, date DATE,
created_by UUID REFERENCES auth.users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_assignments_class ON public.assignments(class_id);
ALTER TABLE public.assignments ENABLE ROW LEVEL SECURITY;
CREATE TABLE IF NOT EXISTS public.grades (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
assignment_id UUID NOT NULL REFERENCES public.assignments(id) ON DELETE CASCADE,
student_id UUID NOT NULL REFERENCES public.students(id) ON DELETE CASCADE,
points NUMERIC, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (assignment_id, student_id)
);
CREATE INDEX IF NOT EXISTS idx_grades_student ON public.grades(student_id);
ALTER TABLE public.grades ENABLE ROW LEVEL SECURITY;
CREATE POLICY "grade_categories read" ON public.grade_categories FOR SELECT TO authenticated USING (TRUE);
CREATE POLICY "grade_categories admin" ON public.grade_categories FOR ALL TO authenticated
USING (public.current_user_has_role('admin')) WITH CHECK (public.current_user_has_role('admin'));
CREATE POLICY "grade_categories teacher" ON public.grade_categories FOR ALL TO authenticated
USING (class_id IS NOT NULL AND public.teaches_class(class_id)) WITH CHECK (class_id IS NOT NULL AND public.teaches_class(class_id));
CREATE POLICY "grade_scale read" ON public.grade_scale FOR SELECT TO authenticated USING (TRUE);
CREATE POLICY "grade_scale admin" ON public.grade_scale FOR ALL TO authenticated
USING (public.current_user_has_role('admin')) WITH CHECK (public.current_user_has_role('admin'));
CREATE POLICY "grade_scale teacher" ON public.grade_scale FOR ALL TO authenticated
USING (class_id IS NOT NULL AND public.teaches_class(class_id)) WITH CHECK (class_id IS NOT NULL AND public.teaches_class(class_id));
CREATE POLICY "assignments read" ON public.assignments FOR SELECT TO authenticated USING (
public.current_user_has_role('admin') OR public.teaches_class(class_id)
OR EXISTS (SELECT 1 FROM public.students s WHERE s.class_id = assignments.class_id AND (public.is_parent_of(s.id) OR public.is_student_account(s.id)))
);
CREATE POLICY "assignments write" ON public.assignments FOR ALL TO authenticated
USING (public.current_user_has_role('admin') OR public.teaches_class(class_id))
WITH CHECK (public.current_user_has_role('admin') OR public.teaches_class(class_id));
CREATE POLICY "grades read" ON public.grades FOR SELECT TO authenticated USING (
public.current_user_has_role('admin') OR public.is_parent_of(student_id) OR public.is_student_account(student_id)
OR EXISTS (SELECT 1 FROM public.assignments a WHERE a.id = grades.assignment_id AND public.teaches_class(a.class_id))
);
CREATE POLICY "grades write" ON public.grades FOR ALL TO authenticated
USING (public.current_user_has_role('admin') OR EXISTS (SELECT 1 FROM public.assignments a WHERE a.id = grades.assignment_id AND public.teaches_class(a.class_id)))
WITH CHECK (public.current_user_has_role('admin') OR EXISTS (SELECT 1 FROM public.assignments a WHERE a.id = grades.assignment_id AND public.teaches_class(a.class_id)));
INSERT INTO public.grade_categories (class_id, name, weight, sort_order)
SELECT v.class_id, v.name, v.weight, v.sort_order
FROM (VALUES (NULL::UUID, 'Homework', 20::NUMERIC, 0), (NULL, 'Quizzes', 30, 1), (NULL, 'Tests', 50, 2)) AS v(class_id, name, weight, sort_order)
WHERE NOT EXISTS (SELECT 1 FROM public.grade_categories WHERE class_id IS NULL);
INSERT INTO public.grade_scale (class_id, letter, min_percent)
SELECT v.class_id, v.letter, v.min_percent
FROM (VALUES (NULL::UUID, 'A', 90::NUMERIC), (NULL, 'B', 80), (NULL, 'C', 70), (NULL, 'D', 60), (NULL, 'F', 0)) AS v(class_id, letter, min_percent)
WHERE NOT EXISTS (SELECT 1 FROM public.grade_scale WHERE class_id IS NULL);