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:
@@ -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('events').select('id'),
|
||||
supabase.from('tickets').select('price'),
|
||||
supabase.from('users').select('id')
|
||||
supabase.from('tickets').select('price')
|
||||
]);
|
||||
|
||||
// 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 events = eventsResult.data?.length || 0;
|
||||
const users = usersResult.data?.length || 0;
|
||||
const tickets = ticketsResult.data || [];
|
||||
const ticketCount = tickets.length;
|
||||
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('users').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5),
|
||||
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 = [];
|
||||
|
||||
// Add recent events
|
||||
@@ -243,22 +275,30 @@ export class AdminApiRouter {
|
||||
}
|
||||
}
|
||||
|
||||
const { data: users, error } = await supabase
|
||||
.from('users')
|
||||
.select(`
|
||||
*,
|
||||
organizations(name)
|
||||
`)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
// Use server-side API endpoint to bypass RLS restrictions
|
||||
const response = await fetch('/api/admin/users', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch users:', response.status, response.statusText);
|
||||
return [];
|
||||
}
|
||||
|
||||
return users || [];
|
||||
} catch (error) {
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Users API error:', result.error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return result.data || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
81
src/pages/api/admin/users.ts
Normal file
81
src/pages/api/admin/users.ts
Normal 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' }
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user