cesjavi commited on
Commit
12b6b78
·
1 Parent(s): d180e64

Fix: Ultimate governance resolution via Database Views (Phase 8)

Browse files
database/FINAL_SUPABASE_FIX.sql CHANGED
@@ -1,42 +1,41 @@
1
- -- FINAL GOVERNANCE & RELATIONSHIP FIX (Phase 8)
2
- -- RUN THIS IN SUPABASE SQL EDITOR TO RESOLVE 400 BAD REQUEST ERRORS
3
-
4
- -- 1. CLEANUP OLD CONSTRAINTS
5
- ALTER TABLE IF EXISTS public.team_members DROP CONSTRAINT IF EXISTS team_members_user_id_fkey;
6
- ALTER TABLE IF EXISTS public.projects DROP CONSTRAINT IF EXISTS projects_owner_id_fkey;
7
- ALTER TABLE IF EXISTS public.agents DROP CONSTRAINT IF EXISTS agents_user_id_fkey;
8
- ALTER TABLE IF EXISTS public.audit_logs DROP CONSTRAINT IF EXISTS audit_logs_actor_id_fkey;
9
- ALTER TABLE IF EXISTS public.audit_logs DROP CONSTRAINT IF EXISTS audit_logs_user_id_fkey;
10
- ALTER TABLE IF EXISTS public.agent_templates DROP CONSTRAINT IF EXISTS agent_templates_author_id_fkey;
11
-
12
- -- 2. CREATE NEW ROBUST RELATIONSHIPS POINTING TO PUBLIC.PROFILES
13
- -- This is critical for PostgREST join discovery.
14
- ALTER TABLE public.team_members
15
- ADD CONSTRAINT team_members_user_id_fkey
16
- FOREIGN KEY (user_id) REFERENCES public.profiles(id) ON DELETE CASCADE;
17
-
18
- ALTER TABLE public.projects
19
- ADD CONSTRAINT projects_owner_id_fkey
20
- FOREIGN KEY (owner_id) REFERENCES public.profiles(id) ON DELETE SET NULL;
21
-
22
- ALTER TABLE public.agents
23
- ADD CONSTRAINT agents_user_id_fkey
24
- FOREIGN KEY (user_id) REFERENCES public.profiles(id) ON DELETE CASCADE;
25
-
26
- ALTER TABLE public.audit_logs
27
- ADD CONSTRAINT audit_logs_user_id_fkey
28
- FOREIGN KEY (user_id) REFERENCES public.profiles(id) ON DELETE SET NULL;
29
-
30
- ALTER TABLE public.agent_templates
31
- ADD CONSTRAINT agent_templates_author_id_fkey
32
- FOREIGN KEY (author_id) REFERENCES public.profiles(id) ON DELETE SET NULL;
33
-
34
- -- 3. ENSURE GRANTS
35
- GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
36
- GRANT USAGE ON SCHEMA public TO authenticated;
37
-
38
- -- 4. RELOAD POSTGREST SCHEMA CACHE
 
 
39
  NOTIFY pgrst, 'reload schema';
40
-
41
- -- 5. VERIFICATION COMMENT (For PostgREST hinting)
42
- COMMENT ON CONSTRAINT team_members_user_id_fkey ON public.team_members IS 'Relationship to profiles table for joins';
 
1
+ -- FINAL ROBUST FIX: DATABASE VIEWS FOR GOVERNANCE (Phase 8)
2
+ -- RUN THIS IN SUPABASE SQL EDITOR TO BYPASS RELATIONSHIP DISCOVERY ISSUES
3
+
4
+ -- 1. TEAM MEMBERS VIEW
5
+ CREATE OR REPLACE VIEW public.team_members_with_profiles AS
6
+ SELECT
7
+ tm.id,
8
+ tm.team_id,
9
+ tm.user_id,
10
+ tm.role,
11
+ tm.created_at,
12
+ p.full_name,
13
+ p.email
14
+ FROM public.team_members tm
15
+ LEFT JOIN public.profiles p ON tm.user_id = p.id;
16
+
17
+ GRANT SELECT ON public.team_members_with_profiles TO authenticated;
18
+
19
+ -- 2. AUDIT LOGS VIEW
20
+ CREATE OR REPLACE VIEW public.audit_logs_with_details AS
21
+ SELECT
22
+ al.id,
23
+ al.user_id,
24
+ al.action,
25
+ al.agent_id,
26
+ al.task_id,
27
+ al.metadata,
28
+ al.created_at,
29
+ p.full_name AS actor_name,
30
+ p.email AS actor_email,
31
+ ag.name AS agent_name,
32
+ t.title AS task_title
33
+ FROM public.audit_logs al
34
+ LEFT JOIN public.profiles p ON al.user_id = p.id
35
+ LEFT JOIN public.agents ag ON al.agent_id = ag.id
36
+ LEFT JOIN public.tasks t ON al.task_id = t.id;
37
+
38
+ GRANT SELECT ON public.audit_logs_with_details TO authenticated;
39
+
40
+ -- 3. RE-SYNC SCHEMA
41
  NOTIFY pgrst, 'reload schema';
 
 
 
frontend/src/components/AuditView.tsx CHANGED
@@ -52,13 +52,8 @@ const AuditView: React.FC = () => {
52
  setLoading(true);
53
  try {
54
  const { data, error: fetchError } = await supabase
55
- .from('audit_logs')
56
- .select(`
57
- *,
58
- profiles!user_id(full_name, email),
59
- agents!agent_id(name),
60
- tasks!task_id(title)
61
- `)
62
  .order('created_at', { ascending: false })
63
  .range(page * pageSize, (page + 1) * pageSize - 1);
64
 
@@ -76,9 +71,9 @@ const AuditView: React.FC = () => {
76
  const rows = logs.map(log => [
77
  log.created_at,
78
  log.action,
79
- (log as any).profiles?.email || 'System',
80
- (log as any).agents?.name || 'N/A',
81
- (log as any).tasks?.title || 'N/A',
82
  JSON.stringify(log.metadata)
83
  ]);
84
 
@@ -93,8 +88,8 @@ const AuditView: React.FC = () => {
93
 
94
  const filteredLogs = logs.filter(log =>
95
  log.action.toLowerCase().includes(searchTerm.toLowerCase()) ||
96
- ((log as any).profiles?.email || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
97
- ((log as any).agents?.name || '').toLowerCase().includes(searchTerm.toLowerCase())
98
  );
99
 
100
  const formatTimestamp = (ts: string) => {
@@ -191,21 +186,21 @@ const AuditView: React.FC = () => {
191
  <td style={{ padding: 'var(--space-md)', fontSize: '0.9rem' }}>
192
  <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
193
  <User size={14} style={{ opacity: 0.5 }} />
194
- {(log as any).profiles?.email || <span style={{ color: 'var(--text-dim)', fontSize: '0.8rem' }}>System</span>}
195
  </div>
196
  </td>
197
  <td style={{ padding: 'var(--space-md)', fontSize: '0.9rem' }}>
198
  <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
199
- {log.agent_id && (log as any).agents && (
200
  <div style={{ display: 'flex', alignItems: 'center', gap: '4px', color: 'var(--accent)', fontSize: '0.8rem' }}>
201
  <Bot size={12} />
202
- {(log as any).agents.name}
203
  </div>
204
  )}
205
- {log.task_id && (log as any).tasks && (
206
  <div style={{ display: 'flex', alignItems: 'center', gap: '4px', color: 'var(--text-dim)', fontSize: '0.8rem' }}>
207
  <FileText size={12} />
208
- {(log as any).tasks.title}
209
  </div>
210
  )}
211
  {!log.agent_id && !log.task_id && <span style={{ color: 'var(--text-dim)', fontSize: '0.8rem' }}>-</span>}
 
52
  setLoading(true);
53
  try {
54
  const { data, error: fetchError } = await supabase
55
+ .from('audit_logs_with_details')
56
+ .select('*')
 
 
 
 
 
57
  .order('created_at', { ascending: false })
58
  .range(page * pageSize, (page + 1) * pageSize - 1);
59
 
 
71
  const rows = logs.map(log => [
72
  log.created_at,
73
  log.action,
74
+ (log as any).actor_email || 'System',
75
+ (log as any).agent_name || 'N/A',
76
+ (log as any).task_title || 'N/A',
77
  JSON.stringify(log.metadata)
78
  ]);
79
 
 
88
 
89
  const filteredLogs = logs.filter(log =>
90
  log.action.toLowerCase().includes(searchTerm.toLowerCase()) ||
91
+ ((log as any).actor_email || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
92
+ ((log as any).agent_name || '').toLowerCase().includes(searchTerm.toLowerCase())
93
  );
94
 
95
  const formatTimestamp = (ts: string) => {
 
186
  <td style={{ padding: 'var(--space-md)', fontSize: '0.9rem' }}>
187
  <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
188
  <User size={14} style={{ opacity: 0.5 }} />
189
+ {(log as any).actor_email || <span style={{ color: 'var(--text-dim)', fontSize: '0.8rem' }}>System</span>}
190
  </div>
191
  </td>
192
  <td style={{ padding: 'var(--space-md)', fontSize: '0.9rem' }}>
193
  <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
194
+ {log.agent_id && (
195
  <div style={{ display: 'flex', alignItems: 'center', gap: '4px', color: 'var(--accent)', fontSize: '0.8rem' }}>
196
  <Bot size={12} />
197
+ {(log as any).agent_name}
198
  </div>
199
  )}
200
+ {log.task_id && (
201
  <div style={{ display: 'flex', alignItems: 'center', gap: '4px', color: 'var(--text-dim)', fontSize: '0.8rem' }}>
202
  <FileText size={12} />
203
+ {(log as any).task_title}
204
  </div>
205
  )}
206
  {!log.agent_id && !log.task_id && <span style={{ color: 'var(--text-dim)', fontSize: '0.8rem' }}>-</span>}
frontend/src/components/TeamsView.tsx CHANGED
@@ -85,19 +85,16 @@ const TeamsView: React.FC = () => {
85
  const fetchMembers = async (teamId: string) => {
86
  try {
87
  const { data, error } = await supabase
88
- .from('team_members')
89
- .select(`
90
- *,
91
- profiles(full_name, email)
92
- `)
93
  .eq('team_id', teamId);
94
 
95
  if (error) throw error;
96
 
97
  const formattedMembers = data.map(m => ({
98
  ...m,
99
- full_name: m.profiles?.full_name,
100
- email: m.profiles?.email
101
  }));
102
 
103
  setMembers(formattedMembers);
 
85
  const fetchMembers = async (teamId: string) => {
86
  try {
87
  const { data, error } = await supabase
88
+ .from('team_members_with_profiles')
89
+ .select('*')
 
 
 
90
  .eq('team_id', teamId);
91
 
92
  if (error) throw error;
93
 
94
  const formattedMembers = data.map(m => ({
95
  ...m,
96
+ full_name: m.full_name,
97
+ email: m.email
98
  }));
99
 
100
  setMembers(formattedMembers);