Comprehensive API documentation for the Elluminar platform, covering authentication, data access, and integration patterns.
Elluminar uses Supabase as the backend-as-a-service, providing:
- Auto-generated REST API from PostgreSQL schema
- Real-time subscriptions via WebSockets
- GraphQL endpoint for flexible queries
- Authentication & authorization with Row Level Security
- File storage & CDN for media assets
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β β β β β
β Flutter Client βββββΊβ Supabase βββββΊβ PostgreSQL β
β β β API Gateway β β Database β
β β’ HTTP Client β β β β β
β β’ WebSocket β β β’ REST API β β β’ Tables β
β β’ Auth Token β β β’ GraphQL β β β’ Views β
β β’ Real-time β β β’ Real-time β β β’ Functions β
β β β β’ Storage β β β’ Triggers β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
// 1. Client authenticates with Supabase
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password'
});
// 2. JWT token automatically included in subsequent requests
const { data: profile } = await supabase
.from('ConsultantProfile')
.select('*')
.eq('userId', data.user.id);- Anonymous: Public data access only
- Authenticated: User-specific data based on RLS policies
- Service Role: Full database access (server-side only)
// Client-side (limited access)
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key' // Row Level Security enforced
);
// Server-side (full access)
const supabaseAdmin = createClient(
'https://your-project.supabase.co',
'your-service-role-key' // Bypasses RLS
);https://your-project.supabase.co/rest/v1/
| Endpoint | Method | Purpose |
|---|---|---|
/auth/v1/signup |
POST | User registration |
/auth/v1/signin |
POST | Email/password login |
/auth/v1/logout |
POST | Sign out user |
/auth/v1/user |
GET | Get current user |
/auth/v1/recover |
POST | Password reset |
| Resource | Endpoint | Methods | Description |
|---|---|---|---|
| Users | /users |
GET, PATCH | User account data |
| Consultant Profiles | /ConsultantProfile |
GET, POST, PATCH | Consultant information |
| Consultee Profiles | /ConsulteeProfile |
GET, POST, PATCH | Client information |
| Domains | /Domain |
GET | Expertise areas |
| Consultations | /Consultation |
GET, POST, PATCH | Consultation sessions |
| Appointments | /Appointment |
GET, POST, PATCH, DELETE | Scheduled meetings |
| Reviews | /ConsultantReview |
GET, POST | Feedback & ratings |
// Get current user profile
const { data: user } = await supabase.auth.getUser();
// Update user profile
const { data, error } = await supabase
.from('users')
.update({
name: 'Updated Name',
avatar_url: 'https://example.com/avatar.jpg'
})
.eq('id', user.id)
.select();// Search consultants by domain
const { data: consultants, error } = await supabase
.from('ConsultantProfile')
.select(`
*,
Domain!inner(name),
users!inner(name, avatar_url)
`)
.eq('Domain.name', 'Technology')
.eq('verified', true)
.order('averageRating', { ascending: false })
.limit(10);// Create new appointment
const { data: appointment, error } = await supabase
.from('Appointment')
.insert({
consultantId: 'consultant-uuid',
consulteeId: 'consultee-uuid',
scheduledAt: '2024-01-15T10:00:00Z',
duration: 60,
type: 'VIDEO_CALL',
status: 'SCHEDULED'
})
.select();
// Get user's appointments
const { data: appointments } = await supabase
.from('Appointment')
.select(`
*,
ConsultantProfile!inner(users(name)),
ConsulteeProfile!inner(users(name))
`)
.or(`consultantId.eq.${userId},consulteeId.eq.${userId}`)
.order('scheduledAt', { ascending: true });// Subscribe to appointment updates
const subscription = supabase
.channel('appointments')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'Appointment',
filter: `consulteeId=eq.${userId}`
},
(payload) => {
console.log('Appointment update:', payload);
// Update UI with new data
}
)
.subscribe();
// Cleanup subscription
subscription.unsubscribe();// Multi-condition search
const { data } = await supabase
.from('ConsultantProfile')
.select('*, Domain(*)')
.gte('averageRating', 4.0)
.lte('hourlyRate', 100)
.in('Domain.name', ['Technology', 'Business'])
.eq('verified', true)
.textSearch('bio', 'react OR flutter', {
type: 'websearch',
config: 'english'
});// Count consultants by domain
const { data } = await supabase
.rpc('count_consultants_by_domain', {
min_rating: 4.0
});
// Custom RPC function in PostgreSQL:
/*
CREATE OR REPLACE FUNCTION count_consultants_by_domain(min_rating decimal)
RETURNS TABLE (domain_name text, consultant_count bigint)
AS $$
BEGIN
RETURN QUERY
SELECT d.name, COUNT(cp.id)
FROM "Domain" d
LEFT JOIN "ConsultantProfile" cp ON d.id = cp."domainId"
WHERE cp."averageRating" >= min_rating
GROUP BY d.name;
END;
$$ LANGUAGE plpgsql;
*/// Bulk insert appointments
const { data, error } = await supabase
.from('Appointment')
.insert([
{ consultantId: 'id1', scheduledAt: '2024-01-15T10:00:00Z' },
{ consultantId: 'id2', scheduledAt: '2024-01-15T11:00:00Z' },
{ consultantId: 'id3', scheduledAt: '2024-01-15T12:00:00Z' }
])
.select();// Subscribe to table changes
const channel = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'Appointment' },
(payload) => console.log(payload)
)
.subscribe();
// Subscribe to specific row
const rowChannel = supabase
.channel('appointment-123')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'Appointment',
filter: 'id=eq.123'
},
handleAppointmentUpdate
)
.subscribe();// Track user presence
const presenceChannel = supabase.channel('consultation-room-123');
presenceChannel
.on('presence', { event: 'sync' }, () => {
const newState = presenceChannel.presenceState();
console.log('Users online:', newState);
})
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
console.log('User joined:', key, newPresences);
})
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
console.log('User left:', key, leftPresences);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await presenceChannel.track({
userId: user.id,
userName: user.name,
onlineAt: new Date().toISOString(),
});
}
});// Upload user avatar
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${user.id}/avatar.jpg`, file, {
upsert: true,
contentType: 'image/jpeg'
});
// Get public URL
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(`${user.id}/avatar.jpg`);// List files in bucket
const { data: files } = await supabase.storage
.from('consultation-docs')
.list(`${consultationId}/`, {
limit: 100,
offset: 0
});
// Delete file
const { error } = await supabase.storage
.from('consultation-docs')
.remove([`${consultationId}/document.pdf`]);-- Example RLS policy for ConsultantProfile
CREATE POLICY "Users can view all verified consultant profiles"
ON "ConsultantProfile"
FOR SELECT
TO authenticated
USING (verified = true);
CREATE POLICY "Consultants can update own profile"
ON "ConsultantProfile"
FOR UPDATE
TO authenticated
USING (userId = auth.uid())
WITH CHECK (userId = auth.uid());- Never expose service role key in client-side code
- Validate user input before database operations
- Use parameterized queries to prevent SQL injection
- Implement rate limiting for sensitive operations
- Audit sensitive operations with triggers/functions
- Use HTTPS everywhere for data in transit
- Regularly rotate API keys and tokens
// Standardized error handling
async function handleApiCall<T>(
apiCall: () => Promise<{ data: T | null; error: any }>
): Promise<T> {
try {
const { data, error } = await apiCall();
if (error) {
// Log error for monitoring
console.error('API Error:', error);
// Handle specific error types
switch (error.code) {
case 'PGRST116': // Row not found
throw new Error('Resource not found');
case '42501': // RLS violation
throw new Error('Access denied');
case '23505': // Unique violation
throw new Error('Resource already exists');
default:
throw new Error(error.message || 'Unknown error');
}
}
return data!;
} catch (error) {
// Network or other errors
if (error instanceof TypeError) {
throw new Error('Network error - check connection');
}
throw error;
}
}// Mock Supabase client for testing
const mockSupabase = {
from: jest.fn(() => ({
select: jest.fn().mockResolvedValue({
data: mockConsultants,
error: null
}),
insert: jest.fn().mockResolvedValue({
data: mockAppointment,
error: null
})
}))
};
// Test API service
test('should fetch consultants', async () => {
const consultants = await getConsultantsByDomain('Technology');
expect(consultants).toHaveLength(3);
expect(consultants[0]).toHaveProperty('verified', true);
});// Test with real Supabase instance
const testSupabase = createClient(
process.env.TEST_SUPABASE_URL!,
process.env.TEST_SUPABASE_ANON_KEY!
);
beforeEach(async () => {
// Clean test data
await testSupabase.from('TestData').delete().neq('id', '');
});- Supabase CLI - Local development
- PostgREST - Auto-generated REST API
- Realtime - WebSocket subscriptions
π Need help with API integration? Check the Getting Started Guide or Troubleshooting!