-- ─────────────────────────────────────────────────────────── -- Profile: avatar + face enrollment -- ─────────────────────────────────────────────────────────── -- avatar_url and face_photo_url may already exist; ADD IF NOT EXISTS -- Postgres 11+ doesn't have ADD COLUMN IF NOT EXISTS in a clean way, -- so we wrap each in a DO block. DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'profiles' AND column_name = 'avatar_url' ) THEN ALTER TABLE profiles ADD COLUMN avatar_url TEXT; END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'profiles' AND column_name = 'face_photo_url' ) THEN ALTER TABLE profiles ADD COLUMN face_photo_url TEXT; END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'profiles' AND column_name = 'face_enrolled_at' ) THEN ALTER TABLE profiles ADD COLUMN face_enrolled_at TIMESTAMPTZ; END IF; END $$; -- ─────────────────────────────────────────────────────────── -- Attendance logs: verification status + selfie -- ─────────────────────────────────────────────────────────── DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'verification_status') THEN CREATE TYPE verification_status AS ENUM ( 'pending', 'verified', 'unverified', 'skipped' ); END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'attendance_logs' AND column_name = 'verification_status' ) THEN ALTER TABLE attendance_logs ADD COLUMN verification_status verification_status NOT NULL DEFAULT 'pending'; END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'attendance_logs' AND column_name = 'verification_photo_url' ) THEN ALTER TABLE attendance_logs ADD COLUMN verification_photo_url TEXT; END IF; END $$; -- ─────────────────────────────────────────────────────────── -- Storage buckets (idempotent via INSERT ... ON CONFLICT) -- ─────────────────────────────────────────────────────────── INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true) ON CONFLICT (id) DO NOTHING; INSERT INTO storage.buckets (id, name, public) VALUES ('face-enrollment', 'face-enrollment', false) ON CONFLICT (id) DO NOTHING; INSERT INTO storage.buckets (id, name, public) VALUES ('attendance-verification', 'attendance-verification', false) ON CONFLICT (id) DO NOTHING; -- Storage policies: avatars (public read, auth write own) DROP POLICY IF EXISTS "Public can view avatars" ON storage.objects; CREATE POLICY "Public can view avatars" ON storage.objects FOR SELECT USING (bucket_id = 'avatars'); DROP POLICY IF EXISTS "Users can upload own avatar" ON storage.objects; CREATE POLICY "Users can upload own avatar" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text ); DROP POLICY IF EXISTS "Users can update own avatar" ON storage.objects; CREATE POLICY "Users can update own avatar" ON storage.objects FOR UPDATE USING ( bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text ); -- Storage policies: face-enrollment (private, owner read/write, admin read) DROP POLICY IF EXISTS "Users can upload own face" ON storage.objects; CREATE POLICY "Users can upload own face" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'face-enrollment' AND (storage.foldername(name))[1] = auth.uid()::text ); DROP POLICY IF EXISTS "Users can view own face" ON storage.objects; CREATE POLICY "Users can view own face" ON storage.objects FOR SELECT USING ( bucket_id = 'face-enrollment' AND ( (storage.foldername(name))[1] = auth.uid()::text OR EXISTS ( SELECT 1 FROM profiles WHERE profiles.id = auth.uid() AND profiles.role = 'admin' ) ) ); -- Storage policies: attendance-verification (owner + admin read, owner write) DROP POLICY IF EXISTS "Users can upload verification photo" ON storage.objects; CREATE POLICY "Users can upload verification photo" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'attendance-verification' AND (storage.foldername(name))[1] = auth.uid()::text ); DROP POLICY IF EXISTS "Users and admins can view verification photos" ON storage.objects; CREATE POLICY "Users and admins can view verification photos" ON storage.objects FOR SELECT USING ( bucket_id = 'attendance-verification' AND ( (storage.foldername(name))[1] = auth.uid()::text OR EXISTS ( SELECT 1 FROM profiles WHERE profiles.id = auth.uid() AND profiles.role IN ('admin', 'dispatcher', 'it_staff') ) ) );