159 lines
5.4 KiB
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')
|
|
)
|
|
)
|
|
);
|