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
Copy
-- 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
Copy
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
Copy
// 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
Copy
// 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
Copy
// 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)
Copy
-- 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
-- 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
// 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 })
}
}