tasq/supabase/migrations/20260307130000_avatar_face_verification.sql

159 lines
5.4 KiB
SQL

-- ───────────────────────────────────────────────────────────
-- 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')
)
)
);