Pulastya B commited on
Commit
1d8f0c9
·
1 Parent(s): 150d34c

Fix auth issues: sign out working, store signup form data, OAuth users onboarding

Browse files
FRRONTEEEND/App.tsx CHANGED
@@ -16,9 +16,17 @@ import { User, LogOut, Loader2 } from 'lucide-react';
16
  // Inner app component that uses auth context
17
  const AppContent: React.FC = () => {
18
  const [view, setView] = useState<'landing' | 'chat' | 'auth'>('landing');
19
- const { user, isAuthenticated, loading, signOut, isConfigured } = useAuth();
20
  const [showUserMenu, setShowUserMenu] = useState(false);
21
 
 
 
 
 
 
 
 
 
22
  // Handle launch console - redirect to auth if not logged in
23
  const handleLaunchConsole = () => {
24
  if (isAuthenticated) {
@@ -91,8 +99,13 @@ const AppContent: React.FC = () => {
91
  </div>
92
  <button
93
  onClick={async () => {
94
- await signOut();
95
- setShowUserMenu(false);
 
 
 
 
 
96
  }}
97
  className="w-full flex items-center gap-2 px-4 py-2 text-sm text-red-400 hover:bg-white/5 transition-colors"
98
  >
 
16
  // Inner app component that uses auth context
17
  const AppContent: React.FC = () => {
18
  const [view, setView] = useState<'landing' | 'chat' | 'auth'>('landing');
19
+ const { user, isAuthenticated, loading, signOut, isConfigured, needsOnboarding } = useAuth();
20
  const [showUserMenu, setShowUserMenu] = useState(false);
21
 
22
+ // If user is authenticated but needs onboarding, show auth page
23
+ React.useEffect(() => {
24
+ if (isAuthenticated && needsOnboarding && view !== 'auth') {
25
+ console.log('User needs onboarding, showing form...');
26
+ setView('auth');
27
+ }
28
+ }, [isAuthenticated, needsOnboarding, view]);
29
+
30
  // Handle launch console - redirect to auth if not logged in
31
  const handleLaunchConsole = () => {
32
  if (isAuthenticated) {
 
99
  </div>
100
  <button
101
  onClick={async () => {
102
+ try {
103
+ await signOut();
104
+ setShowUserMenu(false);
105
+ setView('landing');
106
+ } catch (error) {
107
+ console.error('Sign out failed:', error);
108
+ }
109
  }}
110
  className="w-full flex items-center gap-2 px-4 py-2 text-sm text-red-400 hover:bg-white/5 transition-colors"
111
  >
FRRONTEEEND/components/AuthPage.tsx CHANGED
@@ -26,6 +26,8 @@ import {
26
  import { cn } from "../lib/utils";
27
  import { useAuth } from "../lib/AuthContext";
28
  import { Logo } from "./Logo";
 
 
29
 
30
  const steps = [
31
  { id: "personal", title: "Personal Info" },
@@ -63,7 +65,7 @@ interface AuthPageProps {
63
  }
64
 
65
  export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
66
- const { signIn, signUp, signInWithGoogle, signInWithGithub, isConfigured } = useAuth();
67
  const [mode, setMode] = useState<'signin' | 'signup'>('signin');
68
  const [currentStep, setCurrentStep] = useState(0);
69
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -84,6 +86,18 @@ export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
84
  industry: "",
85
  });
86
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  const updateFormData = (field: keyof FormData, value: string) => {
88
  setFormData((prev) => ({ ...prev, [field]: value }));
89
  setError(null);
@@ -139,22 +153,66 @@ export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
139
  setIsSubmitting(true);
140
  setError(null);
141
 
142
- if (formData.password !== formData.confirmPassword) {
143
- setError("Passwords don't match");
144
- setIsSubmitting(false);
145
- return;
146
- }
147
-
148
  try {
149
- const { error } = await signUp(formData.email, formData.password);
150
- if (error) {
151
- setError(error.message);
 
 
152
  } else {
153
- setSuccess('Account created! Check your email to confirm your account.');
154
- setTimeout(() => {
155
- onSuccess?.();
156
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  } catch (err: any) {
159
  if (err.message?.includes('Failed to fetch')) {
160
  setError('Unable to connect to authentication server. Please try again later.');
@@ -192,8 +250,15 @@ export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
192
  };
193
 
194
  const isStepValid = () => {
 
 
 
195
  switch (currentStep) {
196
  case 0:
 
 
 
 
197
  return formData.name.trim() !== "" && formData.email.trim() !== "" &&
198
  formData.password.length >= 6 && formData.password === formData.confirmPassword;
199
  case 1:
@@ -481,57 +546,66 @@ export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
481
  />
482
  </div>
483
  </motion.div>
484
- <motion.div variants={fadeInUp} className="space-y-2">
485
- <Label htmlFor="signup-password" className="text-white/70">Password</Label>
486
- <div className="relative">
487
- <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
488
- <Input
489
- id="signup-password"
490
- type={showPassword ? "text" : "password"}
491
- placeholder="••••••••"
492
- value={formData.password}
493
- onChange={(e) => updateFormData("password", e.target.value)}
494
- className="pl-10 pr-10 bg-white/5 border-white/10 text-white placeholder:text-white/30 focus:border-indigo-500/50"
495
- />
496
- <button
497
- type="button"
498
- onClick={() => setShowPassword(!showPassword)}
499
- className="absolute right-3 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/50"
500
- >
501
- {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
502
- </button>
503
- </div>
504
- <p className="text-xs text-white/40">Minimum 6 characters</p>
505
- </motion.div>
506
- <motion.div variants={fadeInUp} className="space-y-2">
507
- <Label htmlFor="confirm-password" className="text-white/70">Confirm Password</Label>
508
- <div className="relative">
509
- <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
510
- <Input
511
- id="confirm-password"
512
- type={showPassword ? "text" : "password"}
513
- placeholder="••••••••"
514
- value={formData.confirmPassword}
515
- onChange={(e) => updateFormData("confirmPassword", e.target.value)}
516
- className={cn(
517
- "pl-10 bg-white/5 border-white/10 text-white placeholder:text-white/30 focus:border-indigo-500/50",
518
- formData.confirmPassword && formData.password !== formData.confirmPassword && "border-red-500/50"
 
 
 
 
 
 
 
 
 
519
  )}
520
- />
521
- </div>
522
- {formData.confirmPassword && formData.password !== formData.confirmPassword && (
523
- <p className="text-xs text-red-400">Passwords don't match</p>
524
- )}
525
- </motion.div>
526
 
527
- <div className="relative my-4">
528
- <div className="absolute inset-0 flex items-center">
529
- <div className="w-full border-t border-white/10"></div>
530
- </div>
531
- <div className="relative flex justify-center text-sm">
532
- <span className="px-4 bg-[#0a0a0a] text-white/40">or sign up with</span>
533
- </div>
534
- </div>
 
 
 
535
 
536
  <div className="flex gap-3">
537
  <Button
@@ -560,6 +634,8 @@ export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
560
  GitHub
561
  </Button>
562
  </div>
 
 
563
  </CardContent>
564
  </>
565
  )}
 
26
  import { cn } from "../lib/utils";
27
  import { useAuth } from "../lib/AuthContext";
28
  import { Logo } from "./Logo";
29
+ import { saveUserProfile } from "../lib/supabase";
30
+ import { supabase } from "../lib/supabase";
31
 
32
  const steps = [
33
  { id: "personal", title: "Personal Info" },
 
65
  }
66
 
67
  export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
68
+ const { signIn, signUp, signInWithGoogle, signInWithGithub, isConfigured, user } = useAuth();
69
  const [mode, setMode] = useState<'signin' | 'signup'>('signin');
70
  const [currentStep, setCurrentStep] = useState(0);
71
  const [isSubmitting, setIsSubmitting] = useState(false);
 
86
  industry: "",
87
  });
88
 
89
+ // If user is already authenticated (OAuth), pre-fill email and switch to signup mode for onboarding
90
+ React.useEffect(() => {
91
+ if (user && user.email) {
92
+ setFormData(prev => ({
93
+ ...prev,
94
+ email: user.email || '',
95
+ name: user.user_metadata?.full_name || user.user_metadata?.name || ''
96
+ }));
97
+ setMode('signup');
98
+ }
99
+ }, [user]);
100
+
101
  const updateFormData = (field: keyof FormData, value: string) => {
102
  setFormData((prev) => ({ ...prev, [field]: value }));
103
  setError(null);
 
153
  setIsSubmitting(true);
154
  setError(null);
155
 
156
+ // Check if user is already authenticated (OAuth flow)
157
+ const isOAuthUser = !!user;
158
+
 
 
 
159
  try {
160
+ let userId: string;
161
+
162
+ if (isOAuthUser) {
163
+ // User already authenticated via OAuth, just save profile
164
+ userId = user.id;
165
  } else {
166
+ // Email/password signup
167
+ if (formData.password !== formData.confirmPassword) {
168
+ setError("Passwords don't match");
169
+ setIsSubmitting(false);
170
+ return;
171
+ }
172
+
173
+ const { error } = await signUp(formData.email, formData.password);
174
+ if (error) {
175
+ setError(error.message);
176
+ setIsSubmitting(false);
177
+ return;
178
+ }
179
+
180
+ // Wait for Supabase to create the auth user
181
+ await new Promise(resolve => setTimeout(resolve, 1000));
182
+
183
+ // Get the user ID from auth session
184
+ const { data: { session } } = await supabase.auth.getSession();
185
+ if (!session?.user) {
186
+ setError('Failed to get user session. Please sign in to continue.');
187
+ setIsSubmitting(false);
188
+ return;
189
+ }
190
+ userId = session.user.id;
191
  }
192
+
193
+ // Save user profile data to database
194
+ const profileData = {
195
+ user_id: userId,
196
+ name: formData.name,
197
+ email: formData.email,
198
+ primary_goal: formData.primaryGoal,
199
+ target_outcome: formData.targetOutcome,
200
+ data_types: formData.dataTypes,
201
+ profession: formData.profession,
202
+ experience: formData.experience,
203
+ industry: formData.industry,
204
+ onboarding_completed: true
205
+ };
206
+
207
+ const savedProfile = await saveUserProfile(profileData);
208
+ if (!savedProfile) {
209
+ console.warn('Failed to save profile data, but auth succeeded');
210
+ }
211
+
212
+ setSuccess(isOAuthUser ? 'Profile completed! Redirecting...' : 'Account created successfully! Redirecting...');
213
+ setTimeout(() => {
214
+ onSuccess?.();
215
+ }, 1500);
216
  } catch (err: any) {
217
  if (err.message?.includes('Failed to fetch')) {
218
  setError('Unable to connect to authentication server. Please try again later.');
 
250
  };
251
 
252
  const isStepValid = () => {
253
+ // For OAuth users (already authenticated), skip password validation
254
+ const isOAuthUser = !!user;
255
+
256
  switch (currentStep) {
257
  case 0:
258
+ if (isOAuthUser) {
259
+ // OAuth users don't need password fields
260
+ return formData.name.trim() !== "" && formData.email.trim() !== "";
261
+ }
262
  return formData.name.trim() !== "" && formData.email.trim() !== "" &&
263
  formData.password.length >= 6 && formData.password === formData.confirmPassword;
264
  case 1:
 
546
  />
547
  </div>
548
  </motion.div>
549
+
550
+ {/* Only show password fields for email/password signup (not OAuth) */}
551
+ {!user && (
552
+ <>
553
+ <motion.div variants={fadeInUp} className="space-y-2">
554
+ <Label htmlFor="signup-password" className="text-white/70">Password</Label>
555
+ <div className="relative">
556
+ <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
557
+ <Input
558
+ id="signup-password"
559
+ type={showPassword ? "text" : "password"}
560
+ placeholder="••••••••"
561
+ value={formData.password}
562
+ onChange={(e) => updateFormData("password", e.target.value)}
563
+ className="pl-10 pr-10 bg-white/5 border-white/10 text-white placeholder:text-white/30 focus:border-indigo-500/50"
564
+ />
565
+ <button
566
+ type="button"
567
+ onClick={() => setShowPassword(!showPassword)}
568
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/50"
569
+ >
570
+ {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
571
+ </button>
572
+ </div>
573
+ <p className="text-xs text-white/40">Minimum 6 characters</p>
574
+ </motion.div>
575
+ <motion.div variants={fadeInUp} className="space-y-2">
576
+ <Label htmlFor="confirm-password" className="text-white/70">Confirm Password</Label>
577
+ <div className="relative">
578
+ <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
579
+ <Input
580
+ id="confirm-password"
581
+ type={showPassword ? "text" : "password"}
582
+ placeholder="••••••••"
583
+ value={formData.confirmPassword}
584
+ onChange={(e) => updateFormData("confirmPassword", e.target.value)}
585
+ className={cn(
586
+ "pl-10 bg-white/5 border-white/10 text-white placeholder:text-white/30 focus:border-indigo-500/50",
587
+ formData.confirmPassword && formData.password !== formData.confirmPassword && "border-red-500/50"
588
+ )}
589
+ />
590
+ </div>
591
+ {formData.confirmPassword && formData.password !== formData.confirmPassword && (
592
+ <p className="text-xs text-red-400">Passwords don't match</p>
593
  )}
594
+ </motion.div>
595
+ </>
596
+ )}
 
 
 
597
 
598
+ {/* Only show OAuth buttons for non-authenticated users */}
599
+ {!user && (
600
+ <>
601
+ <div className="relative my-4">
602
+ <div className="absolute inset-0 flex items-center">
603
+ <div className="w-full border-t border-white/10"></div>
604
+ </div>
605
+ <div className="relative flex justify-center text-sm">
606
+ <span className="px-4 bg-[#0a0a0a] text-white/40">or sign up with</span>
607
+ </div>
608
+ </div>
609
 
610
  <div className="flex gap-3">
611
  <Button
 
634
  GitHub
635
  </Button>
636
  </div>
637
+ </>
638
+ )}
639
  </CardContent>
640
  </>
641
  )}
FRRONTEEEND/lib/AuthContext.tsx CHANGED
@@ -1,12 +1,13 @@
1
  import React, { createContext, useContext, useEffect, useState } from 'react';
2
  import { User, Session, AuthChangeEvent } from '@supabase/supabase-js';
3
- import { supabase, startUserSession, endUserSession, isSupabaseConfigured } from './supabase';
4
 
5
  interface AuthContextType {
6
  user: User | null;
7
  session: Session | null;
8
  dbSessionId: string | null;
9
  loading: boolean;
 
10
  signIn: (email: string, password: string) => Promise<{ error: any }>;
11
  signUp: (email: string, password: string) => Promise<{ error: any }>;
12
  signInWithGoogle: () => Promise<{ error: any }>;
@@ -23,6 +24,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
23
  const [session, setSession] = useState<Session | null>(null);
24
  const [dbSessionId, setDbSessionId] = useState<string | null>(null);
25
  const [loading, setLoading] = useState(true);
 
26
  const configured = isSupabaseConfigured();
27
 
28
  useEffect(() => {
@@ -44,6 +46,11 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
44
  if (dbSession) {
45
  setDbSessionId(dbSession.id);
46
  }
 
 
 
 
 
47
  });
48
  }
49
  }).catch((err) => {
@@ -64,12 +71,17 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
64
  if (dbSession) {
65
  setDbSessionId(dbSession.id);
66
  }
 
 
 
 
67
  } else if (event === 'SIGNED_OUT') {
68
  // End tracking session
69
  if (dbSessionId) {
70
  await endUserSession(dbSessionId);
71
  setDbSessionId(null);
72
  }
 
73
  }
74
  }
75
  );
@@ -115,11 +127,20 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
115
  };
116
 
117
  const signOut = async () => {
118
- if (dbSessionId) {
119
- await endUserSession(dbSessionId);
120
- setDbSessionId(null);
 
 
 
 
 
 
 
 
 
 
121
  }
122
- await supabase.auth.signOut();
123
  };
124
 
125
  return (
@@ -129,6 +150,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
129
  session,
130
  dbSessionId,
131
  loading,
 
132
  signIn,
133
  signUp,
134
  signInWithGoogle,
 
1
  import React, { createContext, useContext, useEffect, useState } from 'react';
2
  import { User, Session, AuthChangeEvent } from '@supabase/supabase-js';
3
+ import { supabase, startUserSession, endUserSession, isSupabaseConfigured, getUserProfile } from './supabase';
4
 
5
  interface AuthContextType {
6
  user: User | null;
7
  session: Session | null;
8
  dbSessionId: string | null;
9
  loading: boolean;
10
+ needsOnboarding: boolean;
11
  signIn: (email: string, password: string) => Promise<{ error: any }>;
12
  signUp: (email: string, password: string) => Promise<{ error: any }>;
13
  signInWithGoogle: () => Promise<{ error: any }>;
 
24
  const [session, setSession] = useState<Session | null>(null);
25
  const [dbSessionId, setDbSessionId] = useState<string | null>(null);
26
  const [loading, setLoading] = useState(true);
27
+ const [needsOnboarding, setNeedsOnboarding] = useState(false);
28
  const configured = isSupabaseConfigured();
29
 
30
  useEffect(() => {
 
46
  if (dbSession) {
47
  setDbSessionId(dbSession.id);
48
  }
49
+
50
+ // Check if user needs onboarding
51
+ getUserProfile(session.user.id).then((profile) => {
52
+ setNeedsOnboarding(!profile || !profile.onboarding_completed);
53
+ });
54
  });
55
  }
56
  }).catch((err) => {
 
71
  if (dbSession) {
72
  setDbSessionId(dbSession.id);
73
  }
74
+
75
+ // Check if user needs onboarding
76
+ const profile = await getUserProfile(session.user.id);
77
+ setNeedsOnboarding(!profile || !profile.onboarding_completed);
78
  } else if (event === 'SIGNED_OUT') {
79
  // End tracking session
80
  if (dbSessionId) {
81
  await endUserSession(dbSessionId);
82
  setDbSessionId(null);
83
  }
84
+ setNeedsOnboarding(false);
85
  }
86
  }
87
  );
 
127
  };
128
 
129
  const signOut = async () => {
130
+ try {
131
+ if (dbSessionId) {
132
+ await endUserSession(dbSessionId);
133
+ setDbSessionId(null);
134
+ }
135
+ const { error } = await supabase.auth.signOut();
136
+ if (error) {
137
+ console.error('Sign out error:', error);
138
+ throw error;
139
+ }
140
+ } catch (error) {
141
+ console.error('Sign out failed:', error);
142
+ throw error;
143
  }
 
144
  };
145
 
146
  return (
 
150
  session,
151
  dbSessionId,
152
  loading,
153
+ needsOnboarding,
154
  signIn,
155
  signUp,
156
  signInWithGoogle,
FRRONTEEEND/lib/supabase.ts CHANGED
@@ -206,3 +206,70 @@ export const getUniqueUsersCount = async (days: number = 7) => {
206
  return 0;
207
  }
208
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  return 0;
207
  }
208
  };
209
+
210
+ // User profile management
211
+ export interface UserProfile {
212
+ id?: string;
213
+ user_id: string;
214
+ name: string;
215
+ email: string;
216
+ primary_goal?: string;
217
+ target_outcome?: string;
218
+ data_types?: string[];
219
+ profession?: string;
220
+ experience?: string;
221
+ industry?: string;
222
+ onboarding_completed: boolean;
223
+ created_at?: string;
224
+ updated_at?: string;
225
+ }
226
+
227
+ // Create or update user profile (for signup form data)
228
+ export const saveUserProfile = async (profile: Omit<UserProfile, 'id' | 'created_at' | 'updated_at'>) => {
229
+ try {
230
+ const { data, error } = await supabase
231
+ .from('user_profiles')
232
+ .upsert([{
233
+ ...profile,
234
+ updated_at: new Date().toISOString()
235
+ }], {
236
+ onConflict: 'user_id'
237
+ })
238
+ .select()
239
+ .single();
240
+
241
+ if (error) {
242
+ console.error('Failed to save user profile:', error);
243
+ return null;
244
+ }
245
+ return data;
246
+ } catch (err) {
247
+ console.error('Profile save error:', err);
248
+ return null;
249
+ }
250
+ };
251
+
252
+ // Check if user has completed onboarding
253
+ export const getUserProfile = async (userId: string) => {
254
+ try {
255
+ const { data, error } = await supabase
256
+ .from('user_profiles')
257
+ .select('*')
258
+ .eq('user_id', userId)
259
+ .single();
260
+
261
+ if (error) {
262
+ // User not found is not an error (first time user)
263
+ if (error.code === 'PGRST116') {
264
+ return null;
265
+ }
266
+ console.error('Failed to get user profile:', error);
267
+ return null;
268
+ }
269
+ return data as UserProfile;
270
+ } catch (err) {
271
+ console.error('Profile fetch error:', err);
272
+ return null;
273
+ }
274
+ };
275
+
supabase_schema.sql ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- User Profiles Table
2
+ -- Stores onboarding form data for each user
3
+ CREATE TABLE IF NOT EXISTS user_profiles (
4
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
5
+ user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
6
+ name TEXT NOT NULL,
7
+ email TEXT NOT NULL,
8
+ primary_goal TEXT,
9
+ target_outcome TEXT,
10
+ data_types TEXT[],
11
+ profession TEXT,
12
+ experience TEXT,
13
+ industry TEXT,
14
+ onboarding_completed BOOLEAN DEFAULT FALSE,
15
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
16
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17
+ UNIQUE(user_id)
18
+ );
19
+
20
+ -- Index for faster lookups
21
+ CREATE INDEX IF NOT EXISTS idx_user_profiles_user_id ON user_profiles(user_id);
22
+
23
+ -- RLS (Row Level Security) Policies
24
+ ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
25
+
26
+ -- Allow users to read their own profile
27
+ CREATE POLICY "Users can read own profile" ON user_profiles
28
+ FOR SELECT USING (auth.uid() = user_id);
29
+
30
+ -- Allow users to insert their own profile
31
+ CREATE POLICY "Users can insert own profile" ON user_profiles
32
+ FOR INSERT WITH CHECK (auth.uid() = user_id);
33
+
34
+ -- Allow users to update their own profile
35
+ CREATE POLICY "Users can update own profile" ON user_profiles
36
+ FOR UPDATE USING (auth.uid() = user_id);
37
+
38
+ -- Usage Analytics Table (if not exists)
39
+ CREATE TABLE IF NOT EXISTS usage_analytics (
40
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
41
+ user_id TEXT NOT NULL,
42
+ user_email TEXT,
43
+ session_id TEXT NOT NULL,
44
+ query TEXT NOT NULL,
45
+ agent_used TEXT,
46
+ tools_executed TEXT[],
47
+ tokens_used INTEGER,
48
+ duration_ms INTEGER,
49
+ success BOOLEAN NOT NULL,
50
+ error_message TEXT,
51
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_usage_analytics_user_id ON usage_analytics(user_id);
55
+ CREATE INDEX IF NOT EXISTS idx_usage_analytics_created_at ON usage_analytics(created_at);
56
+
57
+ -- User Sessions Table (if not exists)
58
+ CREATE TABLE IF NOT EXISTS user_sessions (
59
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
60
+ user_id TEXT NOT NULL,
61
+ user_email TEXT,
62
+ started_at TIMESTAMP WITH TIME ZONE NOT NULL,
63
+ ended_at TIMESTAMP WITH TIME ZONE,
64
+ queries_count INTEGER DEFAULT 0,
65
+ browser_info TEXT,
66
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
67
+ );
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
70
+ CREATE INDEX IF NOT EXISTS idx_user_sessions_started_at ON user_sessions(started_at);
71
+
72
+ -- RPC function for incrementing queries count
73
+ CREATE OR REPLACE FUNCTION increment_session_queries(session_id UUID)
74
+ RETURNS VOID AS $$
75
+ BEGIN
76
+ UPDATE user_sessions
77
+ SET queries_count = queries_count + 1
78
+ WHERE id = session_id;
79
+ END;
80
+ $$ LANGUAGE plpgsql;