File size: 3,304 Bytes
aa9892b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
-- ์‚ฌ์šฉ์ž ์˜ค๋ฅ˜ ์‹ ๊ณ  (์šด์ „๋ชจ๋“œ ๋“ฑ์—์„œ "์˜ค๋ฅ˜ ์‹ ๊ณ ํ•˜๊ธฐ" ๋ฒ„ํŠผ)
--
-- ํ™”๋ฉด ์Šคํฌ๋ฆฐ์ƒท + ๊ตฌ์กฐํ™” ๋กœ๊ทธ (DrivingTimingProvider.turnLogs / SttPlugin ์ด๋ฒคํŠธ ๋“ฑ) +
-- ๋””๋ฐ”์ด์Šค ์ปจํ…์ŠคํŠธ (platform / version / route) ๋ฅผ ๋ฐ›์•„ ๋ณด๊ด€. admin /ad โ†’ "์˜ค๋ฅ˜ ์‹ ๊ณ "
-- ํƒญ์—์„œ ๋ชฉ๋ก/์ƒ์„ธ ํ™•์ธ ๊ฐ€๋Šฅ.
--
-- Storage: ์Šคํฌ๋ฆฐ์ƒท์€ ๊ธฐ์กด `scheduled-audio` bucket ์˜ `error-reports/<user_id>/<report_id>.png`
-- ๊ฒฝ๋กœ ์žฌ์‚ฌ์šฉ (๋ณ„๋„ bucket ์•ˆ ๋งŒ๋“ค๊ณ  ๋‹จ์ˆœํ™”).

CREATE TABLE IF NOT EXISTS public.error_reports (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id           UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,

    -- ์‚ฌ์šฉ์ž ์ž…๋ ฅ
    route             TEXT NOT NULL,           -- '/driving', '/message' ๋“ฑ (์ž๋™ ์บก์ฒ˜)
    description       TEXT,                    -- ์„ ํƒ โ€” ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ•œ ์ค„ ๋ฉ”๋ชจ

    -- ์ž๋™ ์ฒจ๋ถ€
    screenshot_path   TEXT,                    -- storage path (signed URL ์œผ๋กœ admin ํ‘œ์‹œ)
    screenshot_url    TEXT,                    -- signed URL (1๋…„)
    logs              JSONB,                   -- ๊ตฌ์กฐํ™” ๋กœ๊ทธ (turnLogs, events ๋“ฑ)
    context           JSONB,                   -- ๋””๋ฐ”์ด์Šค/ํ”Œ๋žซํผ ์ •๋ณด

    -- admin ์ฒ˜๋ฆฌ ์ƒํƒœ
    handled           BOOLEAN NOT NULL DEFAULT FALSE,
    handled_at        TIMESTAMPTZ,
    admin_note        TEXT,                    -- admin ๋ฉ”๋ชจ (ํ•ด๊ฒฐ์ฑ… / ๋ถ„๋ฅ˜)

    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_error_reports_created
    ON public.error_reports (created_at DESC);

CREATE INDEX IF NOT EXISTS idx_error_reports_unhandled
    ON public.error_reports (created_at DESC)
    WHERE handled = FALSE;

ALTER TABLE public.error_reports ENABLE ROW LEVEL SECURITY;

-- ์‚ฌ์šฉ์ž: ๋ณธ์ธ ์‹ ๊ณ ๋งŒ INSERT
DROP POLICY IF EXISTS error_reports_insert ON public.error_reports;
CREATE POLICY error_reports_insert ON public.error_reports
    FOR INSERT
    WITH CHECK (user_id = auth.uid());

-- ์‚ฌ์šฉ์ž: ๋ณธ์ธ ์‹ ๊ณ ๋งŒ SELECT (์ž๊ธฐ ์‹ ๊ณ  ๋ชฉ๋ก ํ™•์ธ์šฉ, ํ–ฅํ›„ ์‚ฌ์šฉ)
DROP POLICY IF EXISTS error_reports_select_own ON public.error_reports;
CREATE POLICY error_reports_select_own ON public.error_reports
    FOR SELECT
    USING (user_id = auth.uid());

-- admin (service_role ์‚ฌ์šฉ) ์€ RLS bypass โ€” ๋ชจ๋“  ์‹ ๊ณ  ์กฐํšŒ/๊ฐฑ์‹  ๊ฐ€๋Šฅ.

CREATE OR REPLACE FUNCTION public._error_reports_touch_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS error_reports_touch_updated_at ON public.error_reports;
CREATE TRIGGER error_reports_touch_updated_at
    BEFORE UPDATE ON public.error_reports
    FOR EACH ROW EXECUTE FUNCTION public._error_reports_touch_updated_at();

COMMENT ON TABLE public.error_reports IS
    '์‚ฌ์šฉ์ž ์˜ค๋ฅ˜ ์‹ ๊ณ  โ€” ํ™”๋ฉด ์Šคํฌ๋ฆฐ์ƒท + ๋กœ๊ทธ + ๋””๋ฐ”์ด์Šค ์ปจํ…์ŠคํŠธ.';
COMMENT ON COLUMN public.error_reports.logs IS
    '๊ตฌ์กฐํ™” ๋กœ๊ทธ. ์šด์ „๋ชจ๋“œ: {"turn_logs": [...], "events": [...]}.';
COMMENT ON COLUMN public.error_reports.context IS
    '๋””๋ฐ”์ด์Šค ์ปจํ…์ŠคํŠธ: {"platform": "ios", "version": "26.4", "app_version": "...", "user_agent": "..."}.';