-- Student photo: a column for the stored path + a private storage bucket. ALTER TABLE public.students ADD COLUMN IF NOT EXISTS photo_path TEXT; INSERT INTO storage.buckets (id, name, public) VALUES ('student-photos', 'student-photos', false) ON CONFLICT (id) DO NOTHING; -- Admins upload/replace/remove photos. CREATE POLICY "student photos admin write" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'student-photos' AND public.current_user_has_role('admin')); CREATE POLICY "student photos admin update" ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'student-photos' AND public.current_user_has_role('admin')); CREATE POLICY "student photos admin delete" ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'student-photos' AND public.current_user_has_role('admin')); -- Read: admins, the student's teacher, or the student's parent. -- Path convention is "/", so folder[1] is the student id. CREATE POLICY "student photos read" ON storage.objects FOR SELECT TO authenticated USING ( bucket_id = 'student-photos' AND ( public.current_user_has_role('admin') OR public.is_parent_of(((storage.foldername(name))[1])::uuid) OR public.teaches_student(((storage.foldername(name))[1])::uuid) ) );