-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsupabase-setup.sql
More file actions
354 lines (318 loc) · 13.7 KB
/
supabase-setup.sql
File metadata and controls
354 lines (318 loc) · 13.7 KB
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
-- ============================================
-- Duo Journal - Supabase Setup
-- Run this SQL in your Supabase SQL Editor
-- ============================================
-- 1. Profiles table
CREATE TABLE IF NOT EXISTS profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
display_name TEXT NOT NULL DEFAULT '',
avatar TEXT NOT NULL DEFAULT '🌸',
created_at TIMESTAMPTZ DEFAULT now()
);
-- 2. Journal entries table
CREATE TABLE IF NOT EXISTS journal_entries (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
date DATE NOT NULL,
content TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(user_id, date)
);
-- 3. Partner requests table
CREATE TABLE IF NOT EXISTS partner_requests (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
from_user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
to_user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'break_pending')),
break_requester_id UUID REFERENCES profiles(id),
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- ============================================
-- Row Level Security
-- ============================================
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE journal_entries ENABLE ROW LEVEL SECURITY;
ALTER TABLE partner_requests ENABLE ROW LEVEL SECURITY;
-- Profiles: anyone can read, users manage own
DROP POLICY IF EXISTS "Anyone can view profiles" ON profiles;
CREATE POLICY "Anyone can view profiles" ON profiles FOR SELECT USING (true);
DROP POLICY IF EXISTS "Users can insert own profile" ON profiles;
CREATE POLICY "Users can insert own profile" ON profiles FOR INSERT WITH CHECK (auth.uid() = id);
DROP POLICY IF EXISTS "Users can update own profile" ON profiles;
CREATE POLICY "Users can update own profile" ON profiles FOR UPDATE USING (auth.uid() = id);
-- Journal entries: users manage own, partners can read
DROP POLICY IF EXISTS "Users can insert own entries" ON journal_entries;
CREATE POLICY "Users can insert own entries" ON journal_entries FOR INSERT WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can update own entries" ON journal_entries;
CREATE POLICY "Users can update own entries" ON journal_entries FOR UPDATE USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can delete own entries" ON journal_entries;
CREATE POLICY "Users can delete own entries" ON journal_entries FOR DELETE USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can read own entries" ON journal_entries;
CREATE POLICY "Users can read own entries" ON journal_entries FOR SELECT USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Partners can read entries" ON journal_entries;
CREATE POLICY "Partners can read entries" ON journal_entries FOR SELECT USING (
EXISTS (
SELECT 1 FROM partner_requests
WHERE status IN ('accepted', 'break_pending')
AND (
(from_user_id = auth.uid() AND to_user_id = journal_entries.user_id)
OR (to_user_id = auth.uid() AND from_user_id = journal_entries.user_id)
)
)
);
-- Partner requests: users see/manage their own
DROP POLICY IF EXISTS "Users can view own requests" ON partner_requests;
CREATE POLICY "Users can view own requests" ON partner_requests FOR SELECT USING (
auth.uid() = from_user_id OR auth.uid() = to_user_id
);
DROP POLICY IF EXISTS "Users can create requests" ON partner_requests;
CREATE POLICY "Users can create requests" ON partner_requests FOR INSERT WITH CHECK (auth.uid() = from_user_id);
DROP POLICY IF EXISTS "Users can update own requests" ON partner_requests;
CREATE POLICY "Users can update own requests" ON partner_requests FOR UPDATE USING (
auth.uid() = from_user_id OR auth.uid() = to_user_id
);
DROP POLICY IF EXISTS "Users can delete own requests" ON partner_requests;
CREATE POLICY "Users can delete own requests" ON partner_requests FOR DELETE USING (
auth.uid() = from_user_id OR auth.uid() = to_user_id
);
-- ============================================
-- Enable Realtime for partner_requests
-- ============================================
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_publication_tables
WHERE pubname = 'supabase_realtime' AND tablename = 'partner_requests'
) THEN
ALTER PUBLICATION supabase_realtime ADD TABLE partner_requests;
END IF;
END $$;
-- ============================================
-- 4. Calendar comments table
-- ============================================
CREATE TABLE IF NOT EXISTS calendar_comments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
date DATE NOT NULL,
comment TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(user_id, date)
);
-- 5. Calendar icons table
CREATE TABLE IF NOT EXISTS calendar_icons (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
date DATE NOT NULL,
icons TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(user_id, date)
);
-- 6. AI comments table
CREATE TABLE IF NOT EXISTS ai_comments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
entry_id UUID REFERENCES journal_entries(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
comment TEXT NOT NULL DEFAULT '',
score INTEGER,
is_public BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(entry_id)
);
-- 7. AI chat messages table
CREATE TABLE IF NOT EXISTS ai_chat_messages (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
ai_comment_id UUID REFERENCES ai_comments(id) ON DELETE CASCADE NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
content TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ DEFAULT now()
);
-- ============================================
-- RLS for new tables
-- ============================================
ALTER TABLE calendar_comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE calendar_icons ENABLE ROW LEVEL SECURITY;
ALTER TABLE ai_comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE ai_chat_messages ENABLE ROW LEVEL SECURITY;
-- Calendar comments: users manage own, partners can read
DROP POLICY IF EXISTS "Users can manage own calendar comments" ON calendar_comments;
CREATE POLICY "Users can manage own calendar comments" ON calendar_comments
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Partners can read calendar comments" ON calendar_comments;
CREATE POLICY "Partners can read calendar comments" ON calendar_comments
FOR SELECT USING (
EXISTS (
SELECT 1 FROM partner_requests
WHERE status IN ('accepted', 'break_pending')
AND (
(from_user_id = auth.uid() AND to_user_id = calendar_comments.user_id)
OR (to_user_id = auth.uid() AND from_user_id = calendar_comments.user_id)
)
)
);
-- Calendar icons: users manage own, partners can read
DROP POLICY IF EXISTS "Users can manage own calendar icons" ON calendar_icons;
CREATE POLICY "Users can manage own calendar icons" ON calendar_icons
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Partners can read calendar icons" ON calendar_icons;
CREATE POLICY "Partners can read calendar icons" ON calendar_icons
FOR SELECT USING (
EXISTS (
SELECT 1 FROM partner_requests
WHERE status IN ('accepted', 'break_pending')
AND (
(from_user_id = auth.uid() AND to_user_id = calendar_icons.user_id)
OR (to_user_id = auth.uid() AND from_user_id = calendar_icons.user_id)
)
)
);
-- AI comments: users manage own, partners can read public ones
DROP POLICY IF EXISTS "Users can manage own AI comments" ON ai_comments;
CREATE POLICY "Users can manage own AI comments" ON ai_comments
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Partners can read public AI comments" ON ai_comments;
CREATE POLICY "Partners can read public AI comments" ON ai_comments
FOR SELECT USING (
is_public = true AND EXISTS (
SELECT 1 FROM partner_requests
WHERE status IN ('accepted', 'break_pending')
AND (
(from_user_id = auth.uid() AND to_user_id = ai_comments.user_id)
OR (to_user_id = auth.uid() AND from_user_id = ai_comments.user_id)
)
)
);
-- AI chat messages: accessible if user owns the parent AI comment
DROP POLICY IF EXISTS "Users can manage own AI chat messages" ON ai_chat_messages;
CREATE POLICY "Users can manage own AI chat messages" ON ai_chat_messages
FOR ALL USING (
EXISTS (SELECT 1 FROM ai_comments WHERE ai_comments.id = ai_chat_messages.ai_comment_id AND ai_comments.user_id = auth.uid())
) WITH CHECK (
EXISTS (SELECT 1 FROM ai_comments WHERE ai_comments.id = ai_chat_messages.ai_comment_id AND ai_comments.user_id = auth.uid())
);
-- ============================================
-- IMPORTANT: Go to Supabase Dashboard -> Auth -> Settings
-- Under "Email Auth", enable "Confirm email" = OFF
-- (or set "Auto Confirm" = ON)
-- This is required because we use username-based auth
-- with generated email addresses.
-- ============================================
-- ============================================
-- 8. Style memory table (per-user AI style adaptation)
-- ============================================
CREATE TABLE IF NOT EXISTS style_memory (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL UNIQUE,
style_preference TEXT NOT NULL DEFAULT 'Auto'
CHECK (style_preference IN ('Auto', 'Poetic', 'Passionate', 'Neutral')),
q_scores JSONB NOT NULL DEFAULT '{"Poetic": 0, "Passionate": 0, "Neutral": 0}',
w_weights JSONB NOT NULL DEFAULT '{"Poetic": 0.333, "Passionate": 0.333, "Neutral": 0.334}',
cooldown_counter INTEGER NOT NULL DEFAULT 0,
last_used_style TEXT,
consecutive_unused JSONB NOT NULL DEFAULT '{"Poetic": 0, "Passionate": 0, "Neutral": 0}',
feedback_log JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
ALTER TABLE style_memory ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can manage own style memory" ON style_memory;
CREATE POLICY "Users can manage own style memory" ON style_memory
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
-- Add style + feedback columns to ai_comments
ALTER TABLE ai_comments ADD COLUMN IF NOT EXISTS style TEXT;
ALTER TABLE ai_comments ADD COLUMN IF NOT EXISTS feedback INTEGER;
-- ============================================
-- RPC: update_ai_comment_visibility
-- Uses POST instead of PATCH for platform compatibility
-- ============================================
CREATE OR REPLACE FUNCTION update_ai_comment_visibility(
comment_id UUID,
new_is_public BOOLEAN
)
RETURNS JSON
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
result ai_comments%ROWTYPE;
BEGIN
UPDATE ai_comments
SET is_public = new_is_public,
updated_at = now()
WHERE id = comment_id
AND user_id = auth.uid()
RETURNING * INTO result;
IF NOT FOUND THEN
RAISE EXCEPTION 'Comment not found or access denied';
END IF;
RETURN row_to_json(result);
END;
$$;
-- Grant execute permission to authenticated users
GRANT EXECUTE ON FUNCTION update_ai_comment_visibility(UUID, BOOLEAN) TO authenticated;
-- ============================================
-- Timetable courses table
-- Stores per-date courses (not weekly recurring)
-- ============================================
DROP TABLE IF EXISTS timetable_courses;
CREATE TABLE timetable_courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
course_date DATE NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
course_name TEXT NOT NULL,
classroom TEXT,
teacher TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_timetable_user_date
ON timetable_courses(user_id, course_date);
ALTER TABLE timetable_courses ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own timetable" ON timetable_courses;
CREATE POLICY "Users can view own timetable"
ON timetable_courses FOR SELECT
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own timetable" ON timetable_courses;
CREATE POLICY "Users can insert own timetable"
ON timetable_courses FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- ============================================
-- RPC: save_timetable_courses
-- Atomically replaces all timetable courses for the current user.
-- Uses POST instead of DELETE+INSERT for platform compatibility.
-- ============================================
DROP FUNCTION IF EXISTS save_timetable_courses(JSONB);
CREATE OR REPLACE FUNCTION save_timetable_courses(courses JSONB)
RETURNS JSON
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
inserted_count INTEGER;
BEGIN
-- Remove old courses
DELETE FROM timetable_courses WHERE user_id = auth.uid();
-- Insert new courses (each item has course_date instead of day_of_week)
INSERT INTO timetable_courses (user_id, course_date, start_time, end_time, course_name, classroom, teacher)
SELECT
auth.uid(),
(item->>'course_date')::DATE,
item->>'start_time',
item->>'end_time',
item->>'course_name',
item->>'classroom',
item->>'teacher'
FROM jsonb_array_elements(courses) AS item;
GET DIAGNOSTICS inserted_count = ROW_COUNT;
RETURN json_build_object('inserted', inserted_count);
END;
$$;
GRANT EXECUTE ON FUNCTION save_timetable_courses(JSONB) TO authenticated;