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('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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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