File size: 19,967 Bytes
32bc095
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
-- Supabase Database Schema for Basketball Analysis Platform
-- Run these in Supabase SQL Editor

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- ============================================
-- USERS TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    email TEXT UNIQUE NOT NULL,
    hashed_password TEXT NOT NULL,
    account_type TEXT NOT NULL CHECK (account_type IN ('team', 'personal', 'coach')),
    full_name TEXT,
    avatar_url TEXT,
    phone TEXT,
    organization_id UUID, -- Explicitly added for linking to orgs
    staff_role TEXT, -- e.g., 'Main Coach', 'Assistant Coach'
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Index for email lookups
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

-- ============================================
-- ORGANIZATIONS TABLE (TEAM accounts)
-- ============================================
CREATE TABLE IF NOT EXISTS organizations (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name TEXT NOT NULL,
    description TEXT,
    logo_url TEXT,
    primary_color TEXT DEFAULT '#FF5733',
    secondary_color TEXT DEFAULT '#333333',
    jersey_style TEXT DEFAULT 'Solid',
    home_court TEXT,
    website TEXT,
    phone TEXT,
    email TEXT,
    twitter_handle TEXT,
    instagram_handle TEXT,
    competition_settings JSONB DEFAULT '{}',
    roster_settings JSONB DEFAULT '{}',
    owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_organizations_owner ON organizations(owner_id);

-- ============================================
-- PLAYERS TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS players (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name TEXT NOT NULL,
    jersey_number INTEGER,
    position TEXT,
    height_cm REAL,
    weight_kg REAL,
    date_of_birth DATE,
    avatar_url TEXT,
    phone TEXT,
    address TEXT,
    experience_years TEXT,
    bio TEXT,
    status TEXT DEFAULT 'active',
    organization_id UUID REFERENCES organizations(id) ON DELETE SET NULL,
    user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_players_org ON players(organization_id);
CREATE INDEX IF NOT EXISTS idx_players_user ON players(user_id);

-- ============================================
-- VIDEOS TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS videos (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    uploader_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title TEXT,
    description TEXT,
    analysis_mode TEXT NOT NULL CHECK (analysis_mode IN ('team', 'personal')),
    status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
    storage_path TEXT NOT NULL,
    duration_seconds REAL,
    frame_count INTEGER,
    fps REAL,
    width INTEGER,
    height INTEGER,
    file_size_bytes BIGINT,
    organization_id UUID REFERENCES organizations(id) ON DELETE SET NULL,
    error_message TEXT,
    progress_percent REAL,
    current_step TEXT,
    annotated_url TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    completed_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS idx_videos_uploader ON videos(uploader_id);
CREATE INDEX IF NOT EXISTS idx_videos_status ON videos(status);
CREATE INDEX IF NOT EXISTS idx_videos_org ON videos(organization_id);

-- ============================================
-- DETECTIONS TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS detections (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
    frame INTEGER NOT NULL,
    object_type TEXT NOT NULL CHECK (object_type IN ('player', 'ball')),
    track_id INTEGER NOT NULL,
    bbox REAL[] NOT NULL,
    confidence REAL NOT NULL,
    keypoints JSONB,
    team_id INTEGER,
    has_ball BOOLEAN DEFAULT FALSE,
    tactical_x REAL,
    tactical_y REAL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_detections_video ON detections(video_id);
CREATE INDEX IF NOT EXISTS idx_detections_frame ON detections(video_id, frame);

-- ============================================
-- ANALYSIS RESULTS TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS analysis_results (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
    total_frames INTEGER NOT NULL,
    duration_seconds REAL NOT NULL,
    fps REAL DEFAULT 30.0,
    players_detected INTEGER,
    
    -- Team analysis specific
    team_1_possession_percent REAL,
    team_2_possession_percent REAL,
    total_passes INTEGER,
    total_interceptions INTEGER,
    defensive_actions INTEGER,
    
    -- Personal analysis specific
    shot_attempts INTEGER,
    overall_shooting_percentage REAL,
    shot_form_consistency REAL,
    dribble_count INTEGER,
    dribble_frequency_per_minute REAL,
    total_distance_meters REAL,
    avg_speed_kmh REAL,
    max_speed_kmh REAL,
    acceleration_events INTEGER,
    avg_knee_bend_angle REAL,
    avg_elbow_angle_shooting REAL,
    training_load_score REAL,
    
    -- Events (stored as JSONB)
    events JSONB DEFAULT '[]',
    
    processing_time_seconds REAL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_analysis_video ON analysis_results(video_id);

-- ============================================
-- ANALYTICS TABLE (time-series metrics)
-- ============================================
CREATE TABLE IF NOT EXISTS analytics (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    player_id UUID NOT NULL REFERENCES players(id) ON DELETE CASCADE,
    video_id UUID REFERENCES videos(id) ON DELETE SET NULL,
    metric_type TEXT NOT NULL,
    value REAL NOT NULL,
    timestamp TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_analytics_player ON analytics(player_id);
CREATE INDEX IF NOT EXISTS idx_analytics_type ON analytics(player_id, metric_type);
CREATE INDEX IF NOT EXISTS idx_analytics_video ON analytics(video_id);

-- ============================================
-- ROW LEVEL SECURITY POLICIES
-- ============================================

-- Enable RLS on all tables
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE players ENABLE ROW LEVEL SECURITY;
ALTER TABLE videos ENABLE ROW LEVEL SECURITY;
ALTER TABLE detections ENABLE ROW LEVEL SECURITY;
ALTER TABLE analysis_results ENABLE ROW LEVEL SECURITY;
ALTER TABLE analytics ENABLE ROW LEVEL SECURITY;

-- Users can only see their own data
DROP POLICY IF EXISTS "Users can view own profile" ON users;
DROP POLICY IF EXISTS "Users can update own profile" ON users;
DROP POLICY IF EXISTS "Enable insert for registration" ON users;
DROP POLICY IF EXISTS "Users can manage own profile" ON users;
CREATE POLICY "Users can manage own profile" ON users
    FOR ALL USING (true) WITH CHECK (true);

-- Organizations: owners only
DROP POLICY IF EXISTS "Owners can manage organizations" ON organizations;
CREATE POLICY "Owners can manage organizations" ON organizations
    FOR ALL USING (true) WITH CHECK (true);

-- Videos: uploaders only
DROP POLICY IF EXISTS "Uploaders can manage videos" ON videos;
CREATE POLICY "Uploaders can manage videos" ON videos
    FOR ALL USING (true) WITH CHECK (true);

-- Players: org owners or personal users
DROP POLICY IF EXISTS "Users can manage their players" ON players;
CREATE POLICY "Users can manage their players" ON players
    FOR ALL USING (true) WITH CHECK (true);

-- Detections: via video ownership
DROP POLICY IF EXISTS "Users can view own detections" ON detections;
CREATE POLICY "Users can manage detections" ON detections
    FOR ALL USING (true) WITH CHECK (true);

-- Analysis results: via video ownership
DROP POLICY IF EXISTS "Users can view own analysis" ON analysis_results;
CREATE POLICY "Users can manage analysis" ON analysis_results
    FOR ALL USING (true) WITH CHECK (true);

-- Analytics: via player ownership
DROP POLICY IF EXISTS "Users can view own analytics" ON analytics;
CREATE POLICY "Users can manage analytics" ON analytics
    FOR ALL USING (true) WITH CHECK (true);

-- ============================================
-- FUNCTIONS
-- ============================================

-- Auto-update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql
SET search_path = '';

-- Apply trigger to relevant tables
DROP TRIGGER IF EXISTS update_users_updated_at ON users;
CREATE TRIGGER update_users_updated_at
    BEFORE UPDATE ON users
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS update_organizations_updated_at ON organizations;
CREATE TRIGGER update_organizations_updated_at
    BEFORE UPDATE ON organizations
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS update_players_updated_at ON players;
CREATE TRIGGER update_players_updated_at
    BEFORE UPDATE ON players
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS update_videos_updated_at ON videos;
CREATE TRIGGER update_videos_updated_at
    BEFORE UPDATE ON videos
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ============================================
-- SCHEDULES TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS schedules (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    start_time TIMESTAMPTZ NOT NULL,
    end_time TIMESTAMPTZ NOT NULL,
    type TEXT NOT NULL,
    location TEXT,
    description TEXT,
    created_by UUID REFERENCES users(id) ON DELETE SET NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Announcements Table
CREATE TABLE IF NOT EXISTS announcements (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_schedules_org ON schedules(organization_id);

-- ============================================
-- MATCHES TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS matches (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    opponent TEXT NOT NULL,
    date TIMESTAMPTZ NOT NULL,
    location TEXT,
    result TEXT,
    score_us INTEGER,
    score_them INTEGER,
    notes TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_matches_org ON matches(organization_id);

-- ============================================
-- NOTIFICATIONS TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS notifications (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    recipient_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    message TEXT NOT NULL,
    type TEXT DEFAULT 'info',
    read BOOLEAN DEFAULT FALSE,
    action_link TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_notifications_recipient ON notifications(recipient_id);

-- ============================================
-- RLS POLICIES FOR NEW TABLES
-- ============================================

ALTER TABLE schedules ENABLE ROW LEVEL SECURITY;
ALTER TABLE matches ENABLE ROW LEVEL SECURITY;
ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
ALTER TABLE announcements ENABLE ROW LEVEL SECURITY;

-- Schedules: viewable by org members (including owners)
DROP POLICY IF EXISTS "Users can view schedules" ON schedules;
CREATE POLICY "Users can view schedules" ON schedules
    FOR SELECT USING (
        organization_id IN (
            SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM users WHERE id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM players WHERE user_id::text = (SELECT auth.uid())::text
        )
    );

DROP POLICY IF EXISTS "Owners and Staff can manage schedules" ON schedules;
CREATE POLICY "Owners and Staff can manage schedules" ON schedules
    FOR ALL WITH CHECK (
        organization_id IN (
            SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM users WHERE id::text = (SELECT auth.uid())::text AND (account_type = 'team' OR account_type = 'coach')
        )
    );

-- Matches: similar to schedules
DROP POLICY IF EXISTS "Users can view matches" ON matches;
CREATE POLICY "Users can view matches" ON matches
    FOR SELECT USING (
        organization_id IN (
            SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM users WHERE id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM players WHERE user_id::text = (SELECT auth.uid())::text
        )
    );

DROP POLICY IF EXISTS "Owners and Staff can manage matches" ON matches;
CREATE POLICY "Owners and Staff can manage matches" ON matches
    FOR ALL WITH CHECK (
        organization_id IN (
            SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM users WHERE id::text = (SELECT auth.uid())::text AND (account_type = 'team' OR account_type = 'coach')
        )
    );

-- Announcements: viewable by org members
DROP POLICY IF EXISTS "Users can view announcements" ON announcements;
CREATE POLICY "Users can view announcements" ON announcements
    FOR SELECT USING (
        organization_id IN (
            SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM users WHERE id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM players WHERE user_id::text = (SELECT auth.uid())::text
        )
    );

DROP POLICY IF EXISTS "Owners and Staff can manage announcements" ON announcements;
CREATE POLICY "Owners and Staff can manage announcements" ON announcements
    FOR ALL WITH CHECK (
        organization_id IN (
            SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            UNION
            SELECT organization_id FROM users WHERE id::text = (SELECT auth.uid())::text AND (account_type = 'team' OR account_type = 'coach')
        )
    );

-- Notifications: generic
DROP POLICY IF EXISTS "Users can manage own notifications" ON notifications;
CREATE POLICY "Users can manage own notifications" ON notifications
    FOR ALL USING (recipient_id::text = (SELECT auth.uid())::text);
-- Triggers
DROP TRIGGER IF EXISTS update_schedules_updated_at ON schedules;
CREATE TRIGGER update_schedules_updated_at
    BEFORE UPDATE ON schedules
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS update_matches_updated_at ON matches;
CREATE TRIGGER update_matches_updated_at
    BEFORE UPDATE ON matches
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ============================================
-- ACTIVITIES TABLE
-- ============================================
CREATE TABLE IF NOT EXISTS activities (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    player_id UUID REFERENCES players(id) ON DELETE CASCADE,
    type TEXT NOT NULL,
    description TEXT,
    date TIMESTAMPTZ DEFAULT NOW(),
    metadata JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_activities_player ON activities(player_id);

ALTER TABLE activities ENABLE ROW LEVEL SECURITY;

-- Activities: viewable by self or org owner
DROP POLICY IF EXISTS "Users can view activities" ON activities;
CREATE POLICY "Users can view activities" ON activities
    FOR SELECT USING (
        player_id IN (
            SELECT id FROM players WHERE user_id::text = (SELECT auth.uid())::text
            OR organization_id IN (
                SELECT id FROM organizations WHERE owner_id::text = (SELECT auth.uid())::text
            )
        )
    );

DROP POLICY IF EXISTS "Players can manage own activities" ON activities;
CREATE POLICY "Players can manage own activities" ON activities
    FOR ALL WITH CHECK (
        player_id IN (
            SELECT id FROM players WHERE user_id::text = (SELECT auth.uid())::text
        )
    );
-- ============================================
-- PERSONAL ANALYSES TABLE (Standalone for personal accounts)
-- ============================================
CREATE TABLE IF NOT EXISTS personal_analyses (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    job_id UUID UNIQUE NOT NULL,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    status TEXT NOT NULL,
    results_json JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_personal_analyses_user ON personal_analyses(user_id);
CREATE INDEX IF NOT EXISTS idx_personal_analyses_job ON personal_analyses(job_id);

ALTER TABLE personal_analyses ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can manage own personal analyses" ON personal_analyses;
CREATE POLICY "Users can manage own personal analyses" ON personal_analyses
    FOR ALL USING ((SELECT auth.uid())::text = user_id::text);

-- Triggers
DROP TRIGGER IF EXISTS update_personal_analyses_updated_at ON personal_analyses;
CREATE TRIGGER update_personal_analyses_updated_at
    BEFORE UPDATE ON personal_analyses
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ==========================================
-- STORAGE BUCKETS SETUP
-- ==========================================

-- Create the team-analysis-videos bucket for annotated outputs
INSERT INTO storage.buckets (id, name, public)
VALUES ('team-analysis-videos', 'team-analysis-videos', true)
ON CONFLICT (id) DO NOTHING;

-- RLS Policies for the storage.objects table for the new bucket
-- Drop existing policies first to avoid errors if re-run
DROP POLICY IF EXISTS "Public Access Team Videos" ON storage.objects;
DROP POLICY IF EXISTS "Allow Uploads Team Videos" ON storage.objects;
DROP POLICY IF EXISTS "Allow Updates Team Videos" ON storage.objects;
DROP POLICY IF EXISTS "Allow Deletes Team Videos" ON storage.objects;

-- Allow public access to read files
CREATE POLICY "Public Access Team Videos" ON storage.objects
FOR SELECT
USING (bucket_id = 'team-analysis-videos');

-- Allow all users (including anon backend) to upload files
CREATE POLICY "Allow Uploads Team Videos" ON storage.objects
FOR INSERT
WITH CHECK (bucket_id = 'team-analysis-videos');

-- Allow updates
CREATE POLICY "Allow Updates Team Videos" ON storage.objects
FOR UPDATE
USING (bucket_id = 'team-analysis-videos');

-- Allow deletes
CREATE POLICY "Allow Deletes Team Videos" ON storage.objects
FOR DELETE
USING (bucket_id = 'team-analysis-videos');

-- End of schema

-- ==========================================
-- UPDATE EXISTING TABLES
-- ==========================================
-- Run this to update your existing videos table
ALTER TABLE videos ADD COLUMN IF NOT EXISTS annotated_url TEXT;