fix: resolve admin dashboard users tab not showing users

- Created new server-side API endpoint /api/admin/users that bypasses RLS
- Updated admin API router to use server-side endpoint instead of client queries
- Fixed issue where client-side Supabase queries were blocked by RLS policies
- Updated platform stats and recent activity to use the new endpoint
- Ensures proper admin authentication and uses service role for data access

Root cause: RLS policies on users table blocked client-side queries from admin dashboard
Solution: Server-side API endpoint with proper admin auth and service role access

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-15 08:56:09 -06:00
parent 1474202a25
commit 6bfe79dcbe
2 changed files with 138 additions and 17 deletions

View File

@@ -64,16 +64,31 @@ export class AdminApiRouter {
} }
} }
const [organizationsResult, eventsResult, ticketsResult, usersResult] = await Promise.all([ const [organizationsResult, eventsResult, ticketsResult] = await Promise.all([
supabase.from('organizations').select('id'), supabase.from('organizations').select('id'),
supabase.from('events').select('id'), supabase.from('events').select('id'),
supabase.from('tickets').select('price'), supabase.from('tickets').select('price')
supabase.from('users').select('id')
]); ]);
// Get user count from API endpoint to bypass RLS
let users = 0;
try {
const usersResponse = await fetch('/api/admin/users', {
method: 'GET',
credentials: 'include'
});
if (usersResponse.ok) {
const usersResult = await usersResponse.json();
if (usersResult.success) {
users = usersResult.data?.length || 0;
}
}
} catch (error) {
console.error('Error fetching user count for stats:', error);
}
const organizations = organizationsResult.data?.length || 0; const organizations = organizationsResult.data?.length || 0;
const events = eventsResult.data?.length || 0; const events = eventsResult.data?.length || 0;
const users = usersResult.data?.length || 0;
const tickets = ticketsResult.data || []; const tickets = ticketsResult.data || [];
const ticketCount = tickets.length; const ticketCount = tickets.length;
const revenue = tickets.reduce((sum, ticket) => sum + (ticket.price || 0), 0); const revenue = tickets.reduce((sum, ticket) => sum + (ticket.price || 0), 0);
@@ -105,12 +120,29 @@ export class AdminApiRouter {
} }
} }
const [eventsResult, usersResult, ticketsResult] = await Promise.all([ const [eventsResult, ticketsResult] = await Promise.all([
supabase.from('events').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5), supabase.from('events').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5),
supabase.from('users').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5),
supabase.from('tickets').select('*, events(title)').order('created_at', { ascending: false }).limit(10) supabase.from('tickets').select('*, events(title)').order('created_at', { ascending: false }).limit(10)
]); ]);
// Get recent users from API endpoint to bypass RLS
let usersResult = { data: [] };
try {
const usersResponse = await fetch('/api/admin/users', {
method: 'GET',
credentials: 'include'
});
if (usersResponse.ok) {
const result = await usersResponse.json();
if (result.success) {
// Limit to 5 most recent users
usersResult.data = result.data.slice(0, 5);
}
}
} catch (error) {
console.error('Error fetching users for recent activity:', error);
}
const activities = []; const activities = [];
// Add recent events // Add recent events
@@ -243,22 +275,30 @@ export class AdminApiRouter {
} }
} }
const { data: users, error } = await supabase // Use server-side API endpoint to bypass RLS restrictions
.from('users') const response = await fetch('/api/admin/users', {
.select(` method: 'GET',
*, credentials: 'include',
organizations(name) headers: {
`) 'Content-Type': 'application/json'
.order('created_at', { ascending: false }); }
});
if (error) {
if (!response.ok) {
console.error('Failed to fetch users:', response.status, response.statusText);
return []; return [];
} }
return users || []; const result = await response.json();
} catch (error) {
if (!result.success) {
console.error('Users API error:', result.error);
return [];
}
return result.data || [];
} catch (error) {
console.error('Error fetching users:', error);
return []; return [];
} }
} }

View File

@@ -0,0 +1,81 @@
import type { APIRoute } from 'astro';
import { createSupabaseServerClient } from '../../../lib/supabase-ssr';
export const GET: APIRoute = async ({ request, cookies }) => {
try {
// Create server-side Supabase client for admin operations
const supabase = createSupabaseServerClient(cookies);
// Get the current session
const { data: { session }, error: sessionError } = await supabase.auth.getSession();
if (sessionError || !session) {
return new Response(
JSON.stringify({ success: false, error: 'Authentication required' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Check if user is admin
const { data: userRecord, error: userError } = await supabase
.from('users')
.select('role')
.eq('id', session.user.id)
.single();
if (userError || !userRecord || userRecord.role !== 'admin') {
return new Response(
JSON.stringify({ success: false, error: 'Admin access required' }),
{
status: 403,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Use service role client for admin queries to bypass RLS
const { createSupabaseAdmin } = await import('../../../lib/supabase-admin');
const serviceClient = createSupabaseAdmin();
// Get all users with organization details
const { data: users, error } = await serviceClient
.from('users')
.select(`
*,
organizations(name)
`)
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching users:', error);
return new Response(
JSON.stringify({ success: false, error: 'Failed to fetch users' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
return new Response(
JSON.stringify({ success: true, data: users || [] }),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
} catch (error) {
console.error('Admin users API error:', error);
return new Response(
JSON.stringify({ success: false, error: 'Internal server error' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
};