Skip to main content

Backend Architecture

TalentG’s backend is built on Supabase, providing a scalable and secure foundation with PostgreSQL database, authentication, and real-time capabilities.

Technology Stack

Core Technologies

  • Supabase: Backend-as-a-Service platform
  • PostgreSQL: Primary database
  • Next.js API Routes: Server-side API endpoints
  • TypeScript: Type-safe development
  • Row Level Security (RLS): Database-level security

Services & Integrations

  • Supabase Auth: User authentication and authorization
  • Supabase Storage: File storage and CDN
  • Supabase Realtime: Real-time subscriptions
  • Brevo: Email delivery service with smart queuing system
  • UploadThing: File upload handling
  • Stripe: Payment processing

Database Architecture

Core Tables

-- User profiles
CREATE TABLE profiles (
  id UUID PRIMARY KEY REFERENCES auth.users(id),
  first_name TEXT,
  last_name TEXT,
  role TEXT CHECK (role IN ('admin', 'manager', 'telecaller', 'user')),
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Leads management
CREATE TABLE leads (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  email TEXT,
  phone TEXT,
  company TEXT,
  status TEXT CHECK (status IN ('new', 'contacted', 'qualified', 'converted', 'lost')),
  source TEXT,
  assigned_to UUID REFERENCES profiles(id),
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Lead interactions
CREATE TABLE interactions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  lead_id UUID REFERENCES leads(id) ON DELETE CASCADE,
  type TEXT CHECK (type IN ('call', 'email', 'meeting', 'note')),
  content TEXT,
  created_by UUID REFERENCES profiles(id),
  created_at TIMESTAMPTZ DEFAULT now()
);

Database Relationships

API Architecture

API Route Structure

app/api/
├── auth/
│   ├── callback/route.ts      # OAuth callback
│   └── refresh/route.ts       # Token refresh
├── leads/
│   ├── route.ts              # GET, POST leads
│   └── [id]/
│       ├── route.ts          # GET, PUT, DELETE lead
│       └── interactions/
│           └── route.ts      # Lead interactions
├── assessments/
│   ├── route.ts              # Assessment CRUD
│   └── [id]/
│       ├── responses/        # Assessment responses
│       └── results/          # Assessment results
└── webhooks/
    ├── stripe/route.ts       # Stripe webhooks
    └── wordpress/route.ts    # WordPress webhooks

API Response Format

// Standard API response format
interface ApiResponse<T = any> {
  success: boolean
  data?: T
  error?: {
    code: string
    message: string
    details?: any
  }
  meta?: {
    total?: number
    page?: number
    limit?: number
  }
}

// Success response
const successResponse = {
  success: true,
  data: { id: '123', name: 'John Doe' },
  meta: { total: 1 }
}

// Error response
const errorResponse = {
  success: false,
  error: {
    code: 'VALIDATION_ERROR',
    message: 'Invalid input data',
    details: { field: 'email', message: 'Invalid email format' }
  }
}

Authentication & Authorization

Supabase Auth Integration

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    persistSession: true,
    autoRefreshToken: true,
  },
})

API Route Authentication

// lib/auth.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextRequest } from 'next/server'

export async function getAuthenticatedUser(request: NextRequest) {
  const supabase = createRouteHandlerClient({ cookies })
  
  const { data: { session } } = await supabase.auth.getSession()
  
  if (!session) {
    throw new Error('Unauthorized')
  }
  
  return session.user
}

export async function requireRole(requiredRole: string) {
  const user = await getAuthenticatedUser()
  
  const { data: profile } = await supabase
    .from('profiles')
    .select('role')
    .eq('id', user.id)
    .single()
  
  if (!profile || !hasRole(profile.role, requiredRole)) {
    throw new Error('Forbidden')
  }
  
  return user
}

Row Level Security (RLS)

-- Enable RLS on all tables
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE leads ENABLE ROW LEVEL SECURITY;
ALTER TABLE interactions ENABLE ROW LEVEL SECURITY;

-- Users can only see their own profile
CREATE POLICY "Users can view own profile" ON profiles
  FOR SELECT USING (auth.uid() = id);

-- Telecallers can see assigned leads
CREATE POLICY "Telecallers see assigned leads" ON leads
  FOR SELECT USING (
    assigned_to = auth.uid() OR 
    EXISTS (
      SELECT 1 FROM profiles 
      WHERE id = auth.uid() AND role IN ('admin', 'manager')
    )
  );

-- Users can create interactions for their leads
CREATE POLICY "Users can create interactions" ON interactions
  FOR INSERT WITH CHECK (
    EXISTS (
      SELECT 1 FROM leads 
      WHERE id = lead_id AND assigned_to = auth.uid()
    )
  );

API Endpoints

Leads Management

// app/api/leads/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { supabase } from '@/lib/supabase'
import { getAuthenticatedUser } from '@/lib/auth'

export async function GET(request: NextRequest) {
  try {
    const user = await getAuthenticatedUser(request)
    
    const { searchParams } = new URL(request.url)
    const page = parseInt(searchParams.get('page') || '1')
    const limit = parseInt(searchParams.get('limit') || '10')
    const status = searchParams.get('status')
    
    let query = supabase
      .from('leads')
      .select('*', { count: 'exact' })
      .order('created_at', { ascending: false })
      .range((page - 1) * limit, page * limit - 1)
    
    if (status) {
      query = query.eq('status', status)
    }
    
    const { data, error, count } = await query
    
    if (error) throw error
    
    return NextResponse.json({
      success: true,
      data,
      meta: {
        total: count,
        page,
        limit
      }
    })
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: {
        code: 'LEADS_FETCH_ERROR',
        message: error.message
      }
    }, { status: 500 })
  }
}

export async function POST(request: NextRequest) {
  try {
    const user = await getAuthenticatedUser(request)
    const body = await request.json()
    
    const { data, error } = await supabase
      .from('leads')
      .insert({
        ...body,
        assigned_to: user.id
      })
      .select()
      .single()
    
    if (error) throw error
    
    return NextResponse.json({
      success: true,
      data
    }, { status: 201 })
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: {
        code: 'LEAD_CREATE_ERROR',
        message: error.message
      }
    }, { status: 400 })
  }
}

Assessment System

// app/api/assessments/route.ts
export async function GET(request: NextRequest) {
  try {
    const user = await getAuthenticatedUser(request)
    
    const { data, error } = await supabase
      .from('assessments')
      .select('*')
      .eq('is_active', true)
      .order('created_at', { ascending: false })
    
    if (error) throw error
    
    return NextResponse.json({
      success: true,
      data
    })
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: {
        code: 'ASSESSMENTS_FETCH_ERROR',
        message: error.message
      }
    }, { status: 500 })
  }
}

export async function POST(request: NextRequest) {
  try {
    const user = await getAuthenticatedUser(request)
    const body = await request.json()
    
    // Validate assessment data
    const assessmentData = {
      title: body.title,
      description: body.description,
      questions: body.questions,
      scoring_rules: body.scoring_rules,
      created_by: user.id
    }
    
    const { data, error } = await supabase
      .from('assessments')
      .insert(assessmentData)
      .select()
      .single()
    
    if (error) throw error
    
    return NextResponse.json({
      success: true,
      data
    }, { status: 201 })
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: {
        code: 'ASSESSMENT_CREATE_ERROR',
        message: error.message
      }
    }, { status: 400 })
  }
}

Real-time Features

Supabase Realtime

// hooks/use-realtime-leads.ts
import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'
import { Lead } from '@/types'

export function useRealtimeLeads() {
  const [leads, setLeads] = useState<Lead[]>([])
  
  useEffect(() => {
    // Subscribe to leads changes
    const subscription = supabase
      .channel('leads')
      .on('postgres_changes', {
        event: '*',
        schema: 'public',
        table: 'leads'
      }, (payload) => {
        console.log('Lead change:', payload)
        
        if (payload.eventType === 'INSERT') {
          setLeads(prev => [payload.new as Lead, ...prev])
        } else if (payload.eventType === 'UPDATE') {
          setLeads(prev => 
            prev.map(lead => 
              lead.id === payload.new.id ? payload.new as Lead : lead
            )
          )
        } else if (payload.eventType === 'DELETE') {
          setLeads(prev => 
            prev.filter(lead => lead.id !== payload.old.id)
          )
        }
      })
      .subscribe()
    
    return () => {
      subscription.unsubscribe()
    }
  }, [])
  
  return leads
}

File Storage

Supabase Storage Integration

// lib/storage.ts
import { supabase } from './supabase'

export async function uploadFile(
  file: File,
  bucket: string,
  path: string
) {
  const { data, error } = await supabase.storage
    .from(bucket)
    .upload(path, file)
  
  if (error) throw error
  
  return data
}

export async function getFileUrl(bucket: string, path: string) {
  const { data } = supabase.storage
    .from(bucket)
    .getPublicUrl(path)
  
  return data.publicUrl
}

export async function deleteFile(bucket: string, path: string) {
  const { error } = await supabase.storage
    .from(bucket)
    .remove([path])
  
  if (error) throw error
}

Error Handling

Global Error Handler

// lib/error-handler.ts
export class AppError extends Error {
  constructor(
    public code: string,
    message: string,
    public statusCode: number = 500,
    public details?: any
  ) {
    super(message)
    this.name = 'AppError'
  }
}

export function handleApiError(error: unknown) {
  if (error instanceof AppError) {
    return {
      success: false,
      error: {
        code: error.code,
        message: error.message,
        details: error.details
      }
    }
  }
  
  // Log unexpected errors
  console.error('Unexpected error:', error)
  
  return {
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred'
    }
  }
}

API Route Error Handling

// app/api/leads/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const user = await getAuthenticatedUser(request)
    const { id } = params
    
    const { data, error } = await supabase
      .from('leads')
      .select('*')
      .eq('id', id)
      .single()
    
    if (error) {
      if (error.code === 'PGRST116') {
        throw new AppError('LEAD_NOT_FOUND', 'Lead not found', 404)
      }
      throw error
    }
    
    return NextResponse.json({
      success: true,
      data
    })
  } catch (error) {
    const errorResponse = handleApiError(error)
    return NextResponse.json(errorResponse, { 
      status: error instanceof AppError ? error.statusCode : 500 
    })
  }
}

Performance Optimization

Database Indexing

-- Performance indexes
CREATE INDEX idx_leads_status ON leads(status);
CREATE INDEX idx_leads_assigned_to ON leads(assigned_to);
CREATE INDEX idx_leads_created_at ON leads(created_at);
CREATE INDEX idx_interactions_lead_id ON interactions(lead_id);
CREATE INDEX idx_assessment_responses_user_id ON assessment_responses(user_id);

Query Optimization

// Optimized query with proper joins
export async function getLeadWithInteractions(leadId: string) {
  const { data, error } = await supabase
    .from('leads')
    .select(`
      *,
      interactions (
        id,
        type,
        content,
        created_at,
        created_by,
        profiles!interactions_created_by_fkey (
          first_name,
          last_name
        )
      )
    `)
    .eq('id', leadId)
    .single()
  
  if (error) throw error
  return data
}

Security Best Practices

Input Validation

// lib/validations.ts
import { z } from 'zod'

export const createLeadSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address').optional(),
  phone: z.string().min(10, 'Phone must be at least 10 digits').optional(),
  company: z.string().optional(),
  source: z.string().optional(),
})

export const updateLeadSchema = createLeadSchema.partial()

Rate Limiting

// lib/rate-limit.ts
import { NextRequest } from 'next/server'

const rateLimitMap = new Map()

export function rateLimit(
  identifier: string,
  limit: number = 100,
  window: number = 60000 // 1 minute
) {
  const now = Date.now()
  const windowStart = now - window
  
  const requests = rateLimitMap.get(identifier) || []
  const validRequests = requests.filter((time: number) => time > windowStart)
  
  if (validRequests.length >= limit) {
    return false
  }
  
  validRequests.push(now)
  rateLimitMap.set(identifier, validRequests)
  
  return true
}

Monitoring & Logging

Structured Logging

// lib/logger.ts
export function logApiCall(
  method: string,
  path: string,
  userId: string,
  statusCode: number,
  duration: number
) {
  console.log(JSON.stringify({
    type: 'api_call',
    method,
    path,
    userId,
    statusCode,
    duration,
    timestamp: new Date().toISOString()
  }))
}

Health Checks

// app/api/health/route.ts
export async function GET() {
  try {
    // Check database connection
    const { data } = await supabase
      .from('profiles')
      .select('count')
      .limit(1)
    
    return NextResponse.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      services: {
        database: 'up',
        auth: 'up'
      }
    })
  } catch (error) {
    return NextResponse.json({
      status: 'unhealthy',
      error: error.message
    }, { status: 500 })
  }
}