-- batch_emails テーブル(予約送信・一斉メール管理)CREATE TABLE IF NOT EXISTS public.batch_emails (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conference_id UUID REFERENCES public.conferences(id) ON DELETE SET NULL,
subject TEXT NOT NULL,
body TEXT NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN (
'all_registrations', 'paid_registrations', 'unpaid_registrations',
'abstract_submitters', 'custom'
)),
target_filter JSONB DEFAULT NULL,
total_count INTEGER NOT NULL DEFAULT 0,
sent_count INTEGER NOT NULL DEFAULT 0,
failed_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT'draft'CHECK (status IN ('draft', 'sending', 'completed', 'cancelled')),
scheduled_at TIMESTAMPTZ DEFAULT NULL,
started_at TIMESTAMPTZ DEFAULT NULL,
completed_at TIMESTAMPTZ DEFAULT NULL,
created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- インデックスCREATE INDEX IF NOT EXISTS idx_batch_emails_conference ON public.batch_emails(conference_id);
CREATE INDEX IF NOT EXISTS idx_batch_emails_status ON public.batch_emails(status);
CREATE INDEX IF NOT EXISTS idx_batch_emails_scheduled ON public.batch_emails(scheduled_at) WHERE scheduled_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_batch_emails_created_by ON public.batch_emails(created_by);
-- updated_at自動更新トリガーCREATE TRIGGER update_batch_emails_updated_at
BEFORE UPDATE ON public.batch_emails
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- RLS有効化ALTER TABLE public.batch_emails ENABLE ROW LEVEL SECURITY;
-- 管理者・運営者のみ全操作CREATE POLICY"batch_emails_admin_all"ON public.batch_emails
FOR ALL USING (
EXISTS (
SELECT 1 FROM public.profiles
WHERE profiles.id = auth.uid()
AND profiles.role IN ('admin', 'organizer')
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM public.profiles
WHERE profiles.id = auth.uid()
AND profiles.role IN ('admin', 'organizer')
)
);
-- サービスロール(Cron用)CREATE POLICY"batch_emails_service_role"ON public.batch_emails
FOR ALL USING (auth.role() = 'service_role')
WITH CHECK (auth.role() = 'service_role');
3-2. notifications テーブル(023_notifications.sql)
-- アプリ内通知テーブルCREATE TABLE IF NOT EXISTS public.notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
type TEXT NOT NULL CHECK (type IN (
'review_request',
'decision_result',
'session_assignment',
'announcement',
'batch_email_completed',
'revision_request',
'system'
)),
title TEXT NOT NULL,
body TEXT NOT NULL,
link TEXT DEFAULT NULL,
is_read BOOLEAN NOT NULL DEFAULT FALSE,
related_id UUID DEFAULT NULL,
related_type TEXT DEFAULT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- インデックスCREATE INDEX IF NOT EXISTS idx_notifications_user_unread ON public.notifications(user_id, is_read) WHERE is_read = FALSE;
CREATE INDEX IF NOT EXISTS idx_notifications_user_created ON public.notifications(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notifications_type ON public.notifications(type);
-- RLS有効化ALTER TABLE public.notifications ENABLE ROW LEVEL SECURITY;
-- 本人の通知のみ閲覧CREATE POLICY"notifications_select_own"ON public.notifications
FOR SELECT USING (user_id = auth.uid());
-- 本人の通知のみ更新(既読化)CREATE POLICY"notifications_update_own"ON public.notifications
FOR UPDATE USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
-- 本人の通知のみ削除CREATE POLICY"notifications_delete_own"ON public.notifications
FOR DELETE USING (user_id = auth.uid());
-- 管理者・運営者がINSERT可能CREATE POLICY"notifications_insert_admin"ON public.notifications
FOR INSERT WITH CHECK (
EXISTS (
SELECT 1 FROM public.profiles
WHERE profiles.id = auth.uid()
AND profiles.role IN ('admin', 'organizer')
)
);
-- サービスロール(Server Actions用)CREATE POLICY"notifications_service_role"ON public.notifications
FOR ALL USING (auth.role() = 'service_role')
WITH CHECK (auth.role() = 'service_role');
-- Web Push購読管理テーブルCREATE TABLE IF NOT EXISTS public.push_subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
endpoint TEXT NOT NULL,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
user_agent TEXT DEFAULT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(user_id, endpoint)
);
-- インデックスCREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_active ON public.push_subscriptions(user_id, is_active) WHERE is_active = TRUE;
-- updated_at自動更新トリガーCREATE TRIGGER update_push_subscriptions_updated_at
BEFORE UPDATE ON public.push_subscriptions
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- RLS有効化ALTER TABLE public.push_subscriptions ENABLE ROW LEVEL SECURITY;
-- 本人のみ操作CREATE POLICY"push_subscriptions_own"ON public.push_subscriptions
FOR ALL USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
-- サービスロール(Push送信時の購読取得用)CREATE POLICY"push_subscriptions_service_role"ON public.push_subscriptions
FOR ALL USING (auth.role() = 'service_role')
WITH CHECK (auth.role() = 'service_role');
-- セッションにライブ配信・アーカイブURL・ハイブリッドフラグを追加ALTER TABLE public.sessions
ADD COLUMN IF NOT EXISTS streaming_url TEXT DEFAULT NULL,
ADD COLUMN IF NOT EXISTS recording_url TEXT DEFAULT NULL,
ADD COLUMN IF NOT EXISTS is_hybrid BOOLEAN NOT NULL DEFAULT FALSE;
3-5. プロフィール公開設定(026_participant_visibility.sql)
-- プロフィール公開設定を追加ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS is_profile_public BOOLEAN NOT NULL DEFAULT FALSE;
3-6. セッションQ&A(027_session_qa.sql)
-- セッションQ&A: 質問テーブルCREATE TABLE IF NOT EXISTS public.session_questions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES public.sessions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
body TEXT NOT NULL,
is_anonymous BOOLEAN NOT NULL DEFAULT FALSE,
is_selected BOOLEAN NOT NULL DEFAULT FALSE,
is_answered BOOLEAN NOT NULL DEFAULT FALSE,
vote_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- セッションQ&A: 投票テーブルCREATE TABLE IF NOT EXISTS public.session_question_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
question_id UUID NOT NULL REFERENCES public.session_questions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(question_id, user_id)
);
-- インデックスCREATE INDEX IF NOT EXISTS idx_session_questions_session_id ON public.session_questions(session_id);
CREATE INDEX IF NOT EXISTS idx_session_question_votes_question_id ON public.session_question_votes(question_id);
-- RLSALTER TABLE public.session_questions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.session_question_votes ENABLE ROW LEVEL SECURITY;
CREATE POLICY"認証ユーザーが質問を閲覧"ON public.session_questions
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY"認証ユーザーが質問を投稿"ON public.session_questions
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY"本人が質問を削除"ON public.session_questions
FOR DELETE USING (auth.uid() = user_id);
CREATE POLICY"管理者が質問を更新"ON public.session_questions
FOR UPDATE USING (
EXISTS (
SELECT 1 FROM public.profiles
WHERE id = auth.uid() AND role IN ('admin', 'organizer')
)
);
CREATE POLICY"認証ユーザーが投票を閲覧"ON public.session_question_votes
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY"認証ユーザーが投票"ON public.session_question_votes
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY"本人が投票を削除"ON public.session_question_votes
FOR DELETE USING (auth.uid() = user_id);
3-7. アンケート(028_surveys.sql)
-- アンケートテーブルCREATE TABLE IF NOT EXISTS public.surveys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conference_id UUID NOT NULL REFERENCES public.conferences(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT,
target_type TEXT NOT NULL DEFAULT'all',
is_active BOOLEAN NOT NULL DEFAULT FALSE,
start_at TIMESTAMPTZ,
end_at TIMESTAMPTZ,
created_by UUID NOT NULL REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- アンケート設問テーブルCREATE TABLE IF NOT EXISTS public.survey_questions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
survey_id UUID NOT NULL REFERENCES public.surveys(id) ON DELETE CASCADE,
question_text TEXT NOT NULL,
question_type TEXT NOT NULL DEFAULT'rating',
options JSONB DEFAULT'[]',
sort_order INTEGER NOT NULL DEFAULT 0,
is_required BOOLEAN NOT NULL DEFAULT TRUE
);
-- 回答テーブルCREATE TABLE IF NOT EXISTS public.survey_responses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
survey_id UUID NOT NULL REFERENCES public.surveys(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(survey_id, user_id)
);
-- 回答詳細テーブルCREATE TABLE IF NOT EXISTS public.survey_answers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
response_id UUID NOT NULL REFERENCES public.survey_responses(id) ON DELETE CASCADE,
question_id UUID NOT NULL REFERENCES public.survey_questions(id) ON DELETE CASCADE,
answer_text TEXT,
answer_rating INTEGER,
answer_choices JSONB DEFAULT'[]'
);
-- インデックスCREATE INDEX IF NOT EXISTS idx_surveys_conference_id ON public.surveys(conference_id);
CREATE INDEX IF NOT EXISTS idx_survey_questions_survey_id ON public.survey_questions(survey_id);
CREATE INDEX IF NOT EXISTS idx_survey_responses_survey_id ON public.survey_responses(survey_id);
CREATE INDEX IF NOT EXISTS idx_survey_answers_response_id ON public.survey_answers(response_id);
-- RLSALTER TABLE public.surveys ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.survey_questions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.survey_responses ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.survey_answers ENABLE ROW LEVEL SECURITY;
CREATE POLICY"認証ユーザーがアンケート閲覧"ON public.surveys
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY"管理者がアンケート管理"ON public.surveys
FOR ALL USING (
EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('admin', 'organizer'))
);
CREATE POLICY"認証ユーザーが設問閲覧"ON public.survey_questions
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY"管理者が設問管理"ON public.survey_questions
FOR ALL USING (
EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('admin', 'organizer'))
);
CREATE POLICY"本人の回答を閲覧"ON public.survey_responses
FOR SELECT USING (auth.uid() = user_id OR EXISTS (
SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('admin', 'organizer')
));
CREATE POLICY"認証ユーザーが回答作成"ON public.survey_responses
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY"回答詳細の閲覧"ON public.survey_answers
FOR SELECT USING (
EXISTS (
SELECT 1 FROM public.survey_responses sr
WHERE sr.id = response_id AND (sr.user_id = auth.uid() OR EXISTS (
SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('admin', 'organizer')
))
)
);
CREATE POLICY"回答詳細の作成"ON public.survey_answers
FOR INSERT WITH CHECK (
EXISTS (
SELECT 1 FROM public.survey_responses sr
WHERE sr.id = response_id AND sr.user_id = auth.uid()
)
);
3-8. ポスターファイル+コメント(029_poster_files.sql)
-- ポスターファイルテーブルCREATE TABLE IF NOT EXISTS public.poster_files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
abstract_id UUID NOT NULL REFERENCES public.abstracts(id) ON DELETE CASCADE,
file_url TEXT NOT NULL,
file_type TEXT NOT NULL DEFAULT'pdf',
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- ポスターコメントテーブルCREATE TABLE IF NOT EXISTS public.poster_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
abstract_id UUID NOT NULL REFERENCES public.abstracts(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- インデックスCREATE INDEX IF NOT EXISTS idx_poster_files_abstract_id ON public.poster_files(abstract_id);
CREATE INDEX IF NOT EXISTS idx_poster_comments_abstract_id ON public.poster_comments(abstract_id);
-- RLSALTER TABLE public.poster_files ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.poster_comments ENABLE ROW LEVEL SECURITY;
CREATE POLICY"認証ユーザーがポスター閲覧"ON public.poster_files
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY"投稿者がポスターアップロード"ON public.poster_files
FOR INSERT WITH CHECK (
EXISTS (
SELECT 1 FROM public.abstracts a
WHERE a.id = abstract_id AND (a.submitter_id = auth.uid() OR EXISTS (
SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('admin', 'organizer')
))
)
);
CREATE POLICY"投稿者がポスター削除"ON public.poster_files
FOR DELETE USING (
EXISTS (
SELECT 1 FROM public.abstracts a
WHERE a.id = abstract_id AND (a.submitter_id = auth.uid() OR EXISTS (
SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('admin', 'organizer')
))
)
);
CREATE POLICY"認証ユーザーがコメント閲覧"ON public.poster_comments
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY"認証ユーザーがコメント投稿"ON public.poster_comments
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY"本人がコメント削除"ON public.poster_comments
FOR DELETE USING (auth.uid() = user_id);