Major fixes and improvements: - Fixed edit event button functionality with proper event handlers and DOM ready state checking - Added status column to tickets table via Supabase migration to resolve 500 API errors - Updated stats API to correctly calculate revenue from decimal price values - Resolved authentication redirect loops by fixing cookie configuration for Docker environment - Fixed Permissions-Policy header syntax errors - Added comprehensive debugging and error handling for event management - Implemented modal-based event editing with form validation and API integration - Enhanced event data loading with proper error handling and user feedback 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
import type { APIRoute } from 'astro';
|
|
import { createSupabaseServerClient } from '../../../lib/supabase-ssr';
|
|
|
|
// Helper function to retry authentication with exponential backoff
|
|
async function retryAuth(supabase: any, email: string, password: string, maxRetries = 3) {
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
const { data, error } = await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
});
|
|
|
|
// If successful or non-rate-limit error, return immediately
|
|
if (!error || (!error.message.includes('over_request_rate_limit') && error.status !== 429)) {
|
|
return { data, error };
|
|
}
|
|
|
|
// If rate limited and not final attempt, wait and retry
|
|
if (attempt < maxRetries) {
|
|
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff: 2s, 4s, 8s
|
|
console.log(`[LOGIN] Rate limited, waiting ${delay}ms before retry ${attempt + 1}`);
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
continue;
|
|
}
|
|
|
|
// Final attempt failed, return the error
|
|
return { data, error };
|
|
} catch (err) {
|
|
// Non-auth errors, return immediately
|
|
return { data: null, error: err };
|
|
}
|
|
}
|
|
}
|
|
|
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
try {
|
|
const formData = await request.json();
|
|
const { email, password } = formData;
|
|
|
|
console.log('[LOGIN] Attempting login for:', email);
|
|
|
|
if (!email || !password) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Email and password are required'
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
const supabase = createSupabaseServerClient(cookies);
|
|
console.log('[LOGIN] Created Supabase client');
|
|
|
|
const { data, error } = await retryAuth(supabase, email, password);
|
|
|
|
console.log('[LOGIN] Supabase response:', {
|
|
hasUser: !!data?.user,
|
|
hasSession: !!data?.session,
|
|
error: error?.message
|
|
});
|
|
|
|
if (error) {
|
|
console.log('[LOGIN] Authentication failed:', error.message);
|
|
|
|
// Handle specific error types
|
|
if (error.message.includes('over_request_rate_limit') || error.status === 429) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Too many login attempts. Please wait 5 minutes and try again.',
|
|
code: 'RATE_LIMITED'
|
|
}), {
|
|
status: 429,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// Handle invalid credentials
|
|
if (error.message.includes('Invalid login credentials') || error.message.includes('Email not confirmed')) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Invalid email or password. Please check your credentials.',
|
|
code: 'INVALID_CREDENTIALS'
|
|
}), {
|
|
status: 401,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// Generic error fallback
|
|
return new Response(JSON.stringify({
|
|
error: error.message || 'Authentication failed',
|
|
code: 'AUTH_ERROR'
|
|
}), {
|
|
status: 401,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// Get user organization
|
|
console.log('[LOGIN] Looking up user data for ID:', data.user.id);
|
|
const { data: userData, error: userError } = await supabase
|
|
.from('users')
|
|
.select('organization_id, role')
|
|
.eq('id', data.user.id)
|
|
.single();
|
|
|
|
console.log('[LOGIN] User data lookup:', {
|
|
userData,
|
|
userError: userError?.message,
|
|
hasOrganization: !!userData?.organization_id,
|
|
userId: data.user.id
|
|
});
|
|
|
|
// If user lookup failed, log detailed error and return error response
|
|
if (userError) {
|
|
console.error('[LOGIN] User lookup failed:', {
|
|
error: userError,
|
|
userId: data.user.id,
|
|
userEmail: data.user.email
|
|
});
|
|
return new Response(JSON.stringify({
|
|
error: 'Failed to retrieve user profile. Please try again.'
|
|
}), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
const redirectTo = !userData?.organization_id
|
|
? '/onboarding/organization'
|
|
: '/dashboard';
|
|
|
|
console.log('[LOGIN] Redirecting to:', redirectTo);
|
|
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
user: data.user,
|
|
organizationId: userData?.organization_id,
|
|
isAdmin: userData?.role === 'admin',
|
|
isSuperAdmin: false, // Super admin logic can be added later if needed
|
|
redirectTo
|
|
}), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
return new Response(JSON.stringify({
|
|
error: 'An error occurred during login'
|
|
}), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}; |