Files
nika-gallery/db/migrations/club_access.sql
T
germanandClaude Opus 4.8 8291d0f725 feat(club): backend закрытого клуба + вход через Яндекс ID
Миграция club_access.sql (access_requests, users.club_tier/yandex_id/
avatar_url, password NULLable, works.club_only). OAuth-роуты Яндекса
(/api/auth/yandex[/callback]) с CSRF-state и мягкой 503 без ключей.
Клуб: /api/club/tiers|request|my-request|gallery + админ requests/
approve/decline. Rate-limit на /api/auth сохранён.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 21:49:02 +03:00

68 lines
3.6 KiB
PL/PgSQL

-- nika-gallery: миграция «Закрытый клуб» + вход через Яндекс ID.
-- Аддитивно к db/schema.sql. Идемпотентна (IF NOT EXISTS / IF EXISTS).
-- Задача: feat/club-yandex-auth (Claude Opus 4.8, 2026-08-05).
--
-- Что добавляет:
-- 1. users: club_tier (уровень клуба), yandex_id (uniq), avatar_url;
-- password делаем NULLable (вход только через Яндекс — без локального пароля).
-- 2. access_requests: заявки в клуб с модерацией (pending/approved/declined).
-- 3. works: флаг club_only (работа видна только членам клуба).
BEGIN;
-- ── users: поля клуба и Яндекс ID ───────────────────────────────
ALTER TABLE users ADD COLUMN IF NOT EXISTS club_tier TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS yandex_id TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS avatar_url TEXT;
-- Допустимые уровни клуба (nullable — большинство пользователей вне клуба).
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'users_club_tier_check'
) THEN
ALTER TABLE users ADD CONSTRAINT users_club_tier_check
CHECK (club_tier IS NULL OR club_tier IN
('vip','collector','partner','photographer','organizer','press'));
END IF;
END $$;
-- yandex_id уникален, но только среди заполненных (частичный уникальный индекс).
CREATE UNIQUE INDEX IF NOT EXISTS uniq_users_yandex_id
ON users(yandex_id) WHERE yandex_id IS NOT NULL;
-- Пароль больше не обязателен: пользователь может входить только через Яндекс.
-- (Локальная регистрация всё равно требует пароль на уровне API.)
ALTER TABLE users ALTER COLUMN password DROP NOT NULL;
-- ── access_requests: заявки в клуб с модерацией ─────────────────
CREATE TABLE IF NOT EXISTS access_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
tier TEXT NOT NULL CHECK (tier IN
('vip','collector','partner','photographer','organizer','press')),
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','approved','declined')),
note TEXT, -- сообщение заявителя «о себе»
admin_note TEXT, -- комментарий модератора (опц.)
reviewed_by UUID REFERENCES users(id),
reviewed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_access_requests_status
ON access_requests(status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_access_requests_user
ON access_requests(user_id, created_at DESC);
-- Не более одной активной (pending) заявки на пользователя.
CREATE UNIQUE INDEX IF NOT EXISTS uniq_access_requests_pending
ON access_requests(user_id) WHERE status = 'pending';
-- ── works: закрытые (клубные) работы ────────────────────────────
ALTER TABLE works ADD COLUMN IF NOT EXISTS club_only BOOLEAN NOT NULL DEFAULT false;
CREATE INDEX IF NOT EXISTS idx_works_club_only ON works(club_only) WHERE club_only = true;
COMMIT;