Files
akmon/create_training_stream_events.sql
2026-01-20 08:04:15 +08:00

143 lines
6.1 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Training stream events table & policies for Supabase realtime integration
-- Generated by GitHub Copilot to support the体育课训练 dashboard mock-to-cloud transition.
CREATE TABLE IF NOT EXISTS public.training_stream_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 记录ID
training_id uuid NOT NULL, -- 训练会话ID未来可关联 training_sessions
event_type VARCHAR(32) NOT NULL, -- 事件类型ack / telemetry / state / summary 等
class_id uuid NOT NULL REFERENCES public.ak_classes(id) ON DELETE CASCADE, -- 班级ID
student_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 学生ID可为空表示班级级别事件
device_id uuid REFERENCES public.ak_devices(id) ON DELETE SET NULL, -- 设备ID
status VARCHAR(64), -- 状态描述,如 online/offline/ack_failed
ack BOOLEAN, -- 是否为应答成功
metrics JSONB, -- 指标明细hr/spo2/temp 等
payload JSONB, -- 额外原始载荷
ingest_source VARCHAR(32) DEFAULT 'gateway', -- 来源gateway/mock/simulator 等
ingest_note TEXT, -- 额外说明,如模拟批次
recorded_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), -- 事件发生时间
ingested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), -- 写入时间
CONSTRAINT training_stream_events_target_chk CHECK (
class_id IS NOT NULL
)
);
COMMENT ON TABLE public.training_stream_events IS '课堂训练实时事件流,供 Supabase realtime 推送给教师端';
COMMENT ON COLUMN public.training_stream_events.training_id IS '训练会话ID同一课堂 session 内共享)';
COMMENT ON COLUMN public.training_stream_events.event_type IS '事件类型ack/telemetry/state/summary 等';
COMMENT ON COLUMN public.training_stream_events.class_id IS '班级ID限定可见范围';
COMMENT ON COLUMN public.training_stream_events.student_id IS '学生ID为空表示班级级别事件';
COMMENT ON COLUMN public.training_stream_events.device_id IS '关联设备;为空表示未绑定或无关设备事件';
COMMENT ON COLUMN public.training_stream_events.metrics IS '结构化指标JSON用于实时展示';
COMMENT ON COLUMN public.training_stream_events.payload IS '原始负载JSON用于调试或回放';
COMMENT ON COLUMN public.training_stream_events.ingest_source IS '事件来源gateway/mock/simulator/service 等';
COMMENT ON COLUMN public.training_stream_events.ingest_note IS '模拟批次、异常说明等';
CREATE INDEX IF NOT EXISTS idx_training_stream_events_training_recorded
ON public.training_stream_events (training_id, recorded_at DESC);
CREATE INDEX IF NOT EXISTS idx_training_stream_events_class_recorded
ON public.training_stream_events (class_id, recorded_at DESC);
CREATE INDEX IF NOT EXISTS idx_training_stream_events_student_recorded
ON public.training_stream_events (student_id, recorded_at DESC);
CREATE INDEX IF NOT EXISTS idx_training_stream_events_event_type
ON public.training_stream_events (event_type);
CREATE INDEX IF NOT EXISTS idx_training_stream_events_device_recorded
ON public.training_stream_events (device_id, recorded_at DESC);
ALTER TABLE public.training_stream_events ENABLE ROW LEVEL SECURITY;
-- 服务端service_role拥有完全访问权限用于 Edge Function / Gateway 写入
CREATE POLICY "service role full access" ON public.training_stream_events
FOR ALL
USING (auth.role() = 'service_role')
WITH CHECK (auth.role() = 'service_role');
-- 教师可以读取自己负责班级的训练事件
CREATE POLICY "teacher read class training events" ON public.training_stream_events
FOR SELECT
TO authenticated
USING (
auth.role() = 'service_role' OR
public.current_user_has_permission('admin.system.manage') OR
public.current_user_has_permission('school_admin.class.manage') OR
EXISTS (
SELECT 1
FROM public.ak_users u
JOIN public.ak_teacher_roles tr ON tr.user_id = u.id
WHERE u.auth_id = auth.uid()
AND tr.class_id = public.training_stream_events.class_id
)
);
-- 学生可查看自己的训练事件
CREATE POLICY "student read own training events" ON public.training_stream_events
FOR SELECT
TO authenticated
USING (
public.current_user_has_permission('student.training.read') AND
EXISTS (
SELECT 1
FROM public.ak_users u
WHERE u.auth_id = auth.uid()
AND u.id = public.training_stream_events.student_id
)
);
DROP POLICY "teacher insert training events" ON public.training_stream_events;
-- 仅允许服务端或具备班级权限的教师写入事件(方便课堂模拟工具)
CREATE POLICY "teacher insert training events" ON public.training_stream_events
FOR INSERT
TO authenticated
WITH CHECK (
auth.role() = 'service_role' OR
EXISTS (
SELECT 1
FROM public.ak_users u
JOIN public.ak_teacher_roles tr ON tr.user_id = u.id
WHERE u.auth_id = auth.uid()
AND tr.class_id = public.training_stream_events.class_id
)
);
-- 清理时只允许服务角色删除
CREATE POLICY "service role delete training events" ON public.training_stream_events
FOR DELETE
USING (auth.role() = 'service_role');
-- 将表加入 Supabase realtime publication避免重复添加导致错误
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_catalog.pg_publication_tables
WHERE pubname = 'supabase_realtime'
AND schemaname = 'public'
AND tablename = 'training_stream_events'
) THEN
EXECUTE 'ALTER PUBLICATION supabase_realtime ADD TABLE public.training_stream_events';
END IF;
END;
$$;
-- 便于查询学生最近一次指标的视图
CREATE OR REPLACE VIEW public.training_stream_latest_metrics AS
SELECT DISTINCT ON (training_id, student_id)
id,
training_id,
class_id,
student_id,
device_id,
event_type,
status,
metrics,
payload,
recorded_at,
ingested_at
FROM public.training_stream_events
WHERE event_type IN ('telemetry', 'metrics')
ORDER BY training_id, student_id, recorded_at DESC, ingested_at DESC, id DESC;
COMMENT ON VIEW public.training_stream_latest_metrics IS '每个学生在当前训练中的最近一次 telemetry/metrics 事件';