# System Architecture Source: https://docs.easelms.org/advanced/architecture Deep dive into EaseLMS architecture, tech stack, and monorepo structure ## Overview EaseLMS is built on a modern, scalable architecture leveraging Next.js 16 with the App Router, TypeScript for type safety, and a monorepo structure managed by Turborepo. The system is designed to be self-hostable while maintaining cloud-native capabilities. ## Architecture Diagram ```text theme={null} ┌─────────────────────────────────────────────────────────────┐ │ Client Layer │ │ Next.js App Router • React 19 • TanStack Query • Tailwind │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ Application Layer │ │ Server Components • API Routes • Server Actions • Middleware│ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ Service Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Supabase │ │ AWS │ │ Payment │ │ Email │ │ │ │ Auth │ │ S3/CDN │ │ Gateways │ │ SendGrid │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ Data Layer │ │ Supabase PostgreSQL with RLS │ └─────────────────────────────────────────────────────────────┘ ``` ## Tech Stack ### Frontend * **Framework**: Next.js 16 with App Router * **Language**: TypeScript 5.0+ * **UI Library**: React 19 * **Styling**: Tailwind CSS * **Components**: Radix UI + shadcn/ui * **State Management**: TanStack Query (React Query) * **Forms**: React Hook Form + Zod validation * **Video Player**: Media Chrome for HLS streaming ### Backend * **Runtime**: Node.js 18.0+ * **Database**: Supabase (PostgreSQL) * **Authentication**: Supabase Auth with Row Level Security (RLS) * **File Storage**: AWS S3 + CloudFront/Azure Front Door CDN * **Video Processing**: AWS MediaConvert for HLS transcoding * **Payments**: Stripe & Flutterwave * **Email**: SendGrid * **PDF Generation**: PDFKit with custom fonts ### Infrastructure * **Monorepo**: Turborepo * **Package Manager**: npm 10.0+ * **Deployment**: Vercel (recommended) or self-hosted * **CDN**: CloudFront or Azure Front Door ## Monorepo Structure EaseLMS uses Turborepo to manage a monorepo with multiple applications: ```text theme={null} easelms/ ├── apps/ │ ├── lms/ # Main LMS application │ │ ├── app/ # Next.js App Router │ │ │ ├── admin/ # Admin dashboard │ │ │ ├── learner/ # Learner interface │ │ │ ├── api/ # API routes │ │ │ └── auth/ # Authentication pages │ │ ├── components/ # React components │ │ ├── lib/ # Utilities and services │ │ └── supabase/ # Database migrations │ └── website/ # Marketing landing page ├── package.json # Root workspace config └── turbo.json # Turborepo configuration ``` easelms/ ├── apps/ │ ├── lms/ # Main LMS application │ │ ├── app/ # Next.js App Router │ │ │ ├── admin/ # Admin dashboard │ │ │ ├── learner/ # Learner interface │ │ │ ├── api/ # API routes │ │ │ └── auth/ # Authentication pages │ │ ├── components/ # React components │ │ ├── lib/ # Core libraries │ │ │ ├── aws/ # S3 & MediaConvert │ │ │ ├── supabase/ # Database & auth clients │ │ │ ├── payments/ # Payment integrations │ │ │ ├── certificates/ # PDF generation │ │ │ ├── email/ # Email service │ │ │ └── react-query/ # Query hooks & cache │ │ ├── hooks/ # Custom React hooks │ │ ├── utils/ # Utility functions │ │ ├── middleware.ts # Auth & CORS middleware │ │ └── supabase/ # Database migrations │ └── website/ # Marketing landing page ├── components/ # Shared components ├── package.json # Root workspace config └── turbo.json # Turborepo configuration ```` ### Workspace Configuration The monorepo uses npm workspaces with Turborepo for build optimization: ```json title="package.json" { "name": "easelms-monorepo", "private": true, "workspaces": ["apps/*"], "scripts": { "dev": "turbo run dev", "build": "turbo run build", "lint": "turbo run lint" } } ```` ### Turborepo Pipeline ```json title="turbo.json" theme={null} { "tasks": { "build": { "dependsOn": ["^build"], "outputs": [".next/**", "!.next/cache/**", "dist/**"] }, "dev": { "cache": false, "persistent": true } } } ``` ## Application Architecture ### Middleware Layer The middleware handles authentication, session management, and CORS: ```typescript title="apps/lms/middleware.ts" theme={null} import { updateSession } from "@/lib/supabase/middleware" import { NextResponse } from "next/server" import type { NextRequest } from "next/server" export async function middleware(request: NextRequest) { // Handle CORS for API routes if (request.nextUrl.pathname.startsWith('/api/')) { const allowedOrigin = process.env.NEXT_PUBLIC_WEBSITE_URL || '*' if (request.method === 'OPTIONS') { return new NextResponse(null, { status: 200, headers: { 'Access-Control-Allow-Origin': allowedOrigin, 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', }, }) } } // Update Supabase session return await updateSession(request) } ``` ### Route Protection Routes are protected based on user type (admin, instructor, learner): * **Public routes**: `/auth/*`, `/forgot-password` * **Admin routes**: `/admin/*` (requires `user_type = 'admin'`) * **Learner routes**: `/learner/*` (requires authenticated user) ### Data Flow 1. **Client Request** → Server Component/API Route 2. **Authentication** → Supabase Auth validates session 3. **Authorization** → Row Level Security (RLS) enforces permissions 4. **Data Fetching** → TanStack Query with cache management 5. **Response** → Server-rendered or JSON response ## Database Schema EaseLMS uses Supabase PostgreSQL with Row Level Security (RLS) policies: ### Core Tables * `profiles` - User profiles with role-based access * `courses` - Course metadata and settings * `lessons` - Course content with video/resources * `enrollments` - Student course enrollments * `progress` - Lesson completion tracking * `payments` - Payment records * `certificates` - Generated certificates * `quiz_questions`, `quiz_attempts`, `quiz_results` - Assessment system * `platform_settings` - Branding and configuration ### Row Level Security All tables use RLS policies to enforce: * Admins can access all data * Instructors can access their courses * Learners can only access enrolled courses * Public can view published course listings ## Caching Strategy TanStack Query manages client-side caching with configurable stale times: ```typescript title="lib/react-query/cache-config.ts" theme={null} export const CACHE_TIMES = { COURSES: 5 * 60 * 1000, // 5 minutes LESSONS: 10 * 60 * 1000, // 10 minutes PROFILE: 30 * 60 * 1000, // 30 minutes SETTINGS: 60 * 60 * 1000, // 1 hour } ``` ### Cache Invalidation * Mutations automatically invalidate related queries * Realtime subscriptions update cache on data changes * Manual invalidation for critical updates ## Deployment Architecture ### Recommended Setup ``` ┌──────────────┐ │ Vercel │ ← Next.js App (SSR + API Routes) └──────┬───────┘ │ ├─────→ Supabase (Database + Auth) ├─────→ AWS S3 (File Storage) ├─────→ CloudFront/Azure FD (CDN) ├─────→ AWS MediaConvert (Video Processing) ├─────→ Stripe/Flutterwave (Payments) └─────→ SendGrid (Emails) ``` ### Scaling Considerations * **Horizontal scaling**: Next.js serverless functions auto-scale * **Database**: Supabase connection pooling * **CDN**: Global edge caching for video/assets * **Video processing**: Async job queue with MediaConvert ## Performance Optimizations 1. **Server Components**: Reduce client-side JavaScript 2. **HLS Streaming**: Adaptive bitrate video delivery 3. **Image Optimization**: Next.js automatic image optimization 4. **Code Splitting**: Dynamic imports for heavy components 5. **Database Indexes**: Optimized queries on frequently accessed tables 6. **CDN Caching**: Static assets served from edge locations ## Security Features * **Authentication**: Supabase Auth with email/password * **Authorization**: Row Level Security (RLS) policies * **CORS**: Configured middleware for cross-origin requests * **Environment Variables**: Secrets managed securely * **Input Validation**: Zod schemas for all forms * **SQL Injection Protection**: Parameterized queries via Supabase client ## Development Workflow ```bash theme={null} # Install dependencies npm install # Start development servers (both apps) npm run dev # Build for production npm run build # Lint code npm run lint ``` ### Environment Setup Each app requires its own `.env.local` file: ```bash title="apps/lms/.env.local" theme={null} NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key SUPABASE_SERVICE_ROLE_KEY=your_service_key AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key ``` ## Next Steps Learn about Supabase authentication implementation Explore AWS S3 and CDN integration Understand video upload and HLS streaming # Authentication System Source: https://docs.easelms.org/advanced/authentication Supabase authentication implementation with row-level security and role-based access control ## Overview EaseLMS uses Supabase Auth for authentication with custom middleware for session management and role-based access control. The system implements three-tier authentication: server-side, middleware, and client-side. ## Architecture ``` ┌─────────────────────────────────────────────────┐ │ Authentication Flow │ ├─────────────────────────────────────────────────┤ │ │ │ 1. User Login → Supabase Auth │ │ 2. Session Created → Cookie Storage │ │ 3. Middleware → Session Validation │ │ 4. RLS Check → Database Access Control │ │ 5. Response → Protected Resource │ │ │ └─────────────────────────────────────────────────┘ ``` ## Supabase Client Types EaseLMS implements three types of Supabase clients for different contexts: ### 1. Server Client Used in Server Components and API Routes with cookie-based session management: ```typescript title="lib/supabase/server.ts" theme={null} import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' export async function createClient() { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY if (!supabaseUrl || !supabaseAnonKey) { throw new Error('Missing Supabase environment variables') } const cookieStore = await cookies() return createServerClient(supabaseUrl, supabaseAnonKey, { cookies: { getAll() { return cookieStore.getAll() }, setAll(cookiesToSet) { try { cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options) ) } catch { // Server Component limitation - middleware handles refresh } }, }, }) } ``` The server client automatically reads session cookies and validates authentication state without requiring manual token management. ### 2. Service Role Client Used for admin operations that bypass Row Level Security: ```typescript title="lib/supabase/server.ts" theme={null} import { createClient as createServiceClient } from '@supabase/supabase-js' export function createServiceRoleClient() { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY if (!supabaseUrl || !supabaseServiceKey) { throw new Error('Missing Supabase service role key') } return createServiceClient(supabaseUrl, supabaseServiceKey, { auth: { autoRefreshToken: false, persistSession: false, }, }) } ``` The service role client bypasses all RLS policies. Use only for trusted server-side operations like user management and admin tasks. ### 3. Browser Client Used in Client Components with automatic session handling: ```typescript title="lib/supabase/client.ts" theme={null} import { createBrowserClient } from '@supabase/ssr' import { checkSupabaseEnv } from './env-check' export function createClient() { const envCheck = checkSupabaseEnv() if (!envCheck.valid) { if (process.env.NODE_ENV === 'development') { console.error(envCheck.message) } // Fallback to placeholder to prevent crashes return createBrowserClient( 'https://placeholder.supabase.co', 'placeholder-key' ) } const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! return createBrowserClient(supabaseUrl, supabaseAnonKey) } ``` ## Middleware Implementation The middleware handles session validation and route protection: ```typescript title="lib/supabase/middleware.ts" theme={null} import { createServerClient } from '@supabase/ssr' import { NextResponse, type NextRequest } from 'next/server' export async function updateSession(request: NextRequest) { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY if (!supabaseUrl || !supabaseAnonKey) { return NextResponse.next({ request }) } let supabaseResponse = NextResponse.next({ request }) const supabase = createServerClient(supabaseUrl, supabaseAnonKey, { cookies: { getAll() { return request.cookies.getAll() }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value) ) supabaseResponse = NextResponse.next({ request }) cookiesToSet.forEach(({ name, value, options }) => supabaseResponse.cookies.set(name, value, options) ) }, }, }) // Get authenticated user const { data: { user } } = await supabase.auth.getUser() // Public paths that don't require authentication const publicPaths = ['/auth', '/forgot-password'] const isPublicPath = publicPaths.some(path => request.nextUrl.pathname.startsWith(path) ) // Redirect unauthenticated users if (!user && !isPublicPath && !request.nextUrl.pathname.startsWith('/api')) { const url = request.nextUrl.clone() url.pathname = request.nextUrl.pathname.startsWith('/admin') ? '/auth/admin/login' : '/auth/learner/login' return NextResponse.redirect(url) } return supabaseResponse } ``` ## User Roles & Authorization EaseLMS implements role-based access control with three user types: ### User Types ```sql theme={null} user_type = 'admin' ``` **Permissions:** * Full system access * User management * Course creation and management * Payment and enrollment management * Platform settings configuration * Analytics access ```sql theme={null} user_type = 'instructor' ``` **Permissions:** * Create and manage own courses * View student enrollments in their courses * Access course analytics * Manage course content ```sql theme={null} user_type = 'user' ``` **Permissions:** * Enroll in courses * Access enrolled course content * Track progress * Download certificates * View personal dashboard ### Role Check in Middleware ```typescript title="middleware.ts (excerpt)" theme={null} // Get user type from profile const { data: profile } = await supabase .from('profiles') .select('user_type') .eq('id', user.id) .single() const userType = profile?.user_type || 'user' // Protect admin routes if (request.nextUrl.pathname.startsWith('/admin') && userType !== 'admin') { url.pathname = '/auth/admin/login' return NextResponse.redirect(url) } ``` ## Row Level Security (RLS) Supabase RLS policies enforce authorization at the database level: ### Example: Courses Table ```sql theme={null} -- Admins can do anything CREATE POLICY "Admins have full access to courses" ON courses FOR ALL USING (auth.uid() IN ( SELECT id FROM profiles WHERE user_type = 'admin' )); -- Instructors can manage their courses CREATE POLICY "Instructors can manage their courses" ON courses FOR ALL USING (auth.uid() IN ( SELECT user_id FROM course_instructors WHERE course_id = courses.id )); -- Public can view published courses CREATE POLICY "Anyone can view published courses" ON courses FOR SELECT USING (published = true); ``` ### Example: Enrollments Table ```sql theme={null} -- Users can view their own enrollments CREATE POLICY "Users can view their own enrollments" ON enrollments FOR SELECT USING (auth.uid() = user_id); -- Admins can view all enrollments CREATE POLICY "Admins can view all enrollments" ON enrollments FOR SELECT USING (auth.uid() IN ( SELECT id FROM profiles WHERE user_type = 'admin' )); ``` ## Authentication Flows ### Login Flow ```typescript theme={null} import { createClient } from '@/lib/supabase/client' async function handleLogin(email: string, password: string) { const supabase = createClient() const { data, error } = await supabase.auth.signInWithPassword({ email, password, }) if (error) { throw new Error(error.message) } // Session is automatically stored in cookies // Middleware will handle redirection return data } ``` ### Signup Flow ```typescript theme={null} async function handleSignup(email: string, password: string, userData: any) { const supabase = createClient() // Create auth user const { data: authData, error: authError } = await supabase.auth.signUp({ email, password, }) if (authError) throw authError // Create profile (triggered by database trigger or manual insert) const { error: profileError } = await supabase .from('profiles') .insert({ id: authData.user!.id, email, full_name: userData.full_name, user_type: 'user', // Default to learner }) if (profileError) throw profileError return authData } ``` ### Logout Flow ```typescript theme={null} async function handleLogout() { const supabase = createClient() const { error } = await supabase.auth.signOut() if (error) throw error // Middleware will redirect to login window.location.href = '/auth/learner/login' } ``` ### Password Reset Flow ```typescript theme={null} async function requestPasswordReset(email: string) { const supabase = createClient() const { error } = await supabase.auth.resetPasswordForEmail(email, { redirectTo: `${window.location.origin}/auth/reset-password`, }) if (error) throw error } async function updatePassword(newPassword: string) { const supabase = createClient() const { error } = await supabase.auth.updateUser({ password: newPassword, }) if (error) throw error } ``` ## Session Management ### Auto-Refresh Supabase Auth automatically refreshes sessions before expiry: ```typescript theme={null} const supabase = createClient() // Listen for auth state changes supabase.auth.onAuthStateChange((event, session) => { if (event === 'SIGNED_OUT') { // Clear local data } else if (event === 'TOKEN_REFRESHED') { // Session refreshed automatically } }) ``` ### Manual Session Check ```typescript theme={null} async function getSession() { const supabase = createClient() const { data: { session } } = await supabase.auth.getSession() return session } async function getUser() { const supabase = createClient() const { data: { user } } = await supabase.auth.getUser() return user } ``` ## Security Best Practices Never commit credentials to version control: ```bash theme={null} # Public (safe for client-side) NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # Private (server-side only) SUPABASE_SERVICE_ROLE_KEY=eyJ... ``` Only use service role client for: * User management operations * Admin-only database operations * Bypassing RLS when necessary Never expose service role key to client-side code. Always enable RLS on all tables and create policies for: * Read access (SELECT) * Write access (INSERT, UPDATE, DELETE) * Admin override Supabase automatically sets secure cookies: * HttpOnly flag prevents XSS attacks * Secure flag ensures HTTPS-only * SameSite prevents CSRF attacks ## Debugging Authentication ### Enable Debug Logging ```typescript title="middleware.ts" theme={null} if (process.env.NODE_ENV === 'development') { console.log('Middleware user type check:', { userId: user.id, userType, profileExists: !!profile, pathname: request.nextUrl.pathname, }) } ``` ### Common Issues Check that cookies are enabled and HTTPS is used in production. Verify RLS policies match your user's role and use service role client for admin operations. Ensure middleware doesn't redirect authenticated users accessing valid routes. Verify profile is created on signup (use database trigger or manual insert). ## Next Steps Learn about S3 integration for user uploads Understand video transcoding and streaming # File Storage & CDN Source: https://docs.easelms.org/advanced/file-storage AWS S3 integration with CloudFront/Azure Front Door for scalable file storage and delivery ## Overview EaseLMS uses AWS S3 for object storage with optional CDN integration (CloudFront or Azure Front Door) for global content delivery. The system supports videos, images, documents, and certificates with intelligent path management and deduplication. ## Storage Architecture ``` ┌─────────────────────────────────────────────────┐ │ Upload Flow │ ├─────────────────────────────────────────────────┤ │ │ │ 1. Client → Presigned URL Request │ │ 2. Server → Generate Presigned URL │ │ 3. Client → Direct Upload to S3 │ │ 4. S3 → Store Object │ │ 5. MediaConvert → Transcode Video (if video) │ │ 6. CDN → Cache for Global Delivery │ │ │ └─────────────────────────────────────────────────┘ ``` ## S3 Client Configuration The S3 client is configured with AWS SDK v3: ```typescript title="lib/aws/s3.ts" theme={null} import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3" import { getSignedUrl } from "@aws-sdk/s3-request-presigner" const s3Client = new S3Client({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }) const BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME! ``` ### Environment Variables ```bash title=".env.local" theme={null} AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key AWS_S3_BUCKET_NAME=your_bucket_name # Optional CDN AWS_CLOUDFRONT_DOMAIN=d123456.cloudfront.net # OR AZURE_CDN_URL=https://your-endpoint.azurefd.net USE_AZURE_CDN=true ``` ## File Organization Structure EaseLMS uses a hierarchical S3 key structure for organization: ``` s3://bucket-name/ ├── courses/ │ └── course-{id}/ │ ├── thumbnail-{id}-{filename} │ ├── preview-video-{id}-{filename} │ ├── hls/ │ │ └── preview-video-{id}/ │ │ ├── preview-video-{id}.m3u8 │ │ ├── preview-video-{id}_1080p.m3u8 │ │ ├── preview-video-{id}_720p.m3u8 │ │ └── *.ts segments │ ├── lessons/ │ │ └── lesson-{id}/ │ │ ├── video-{id}-{filename} │ │ ├── hls/ │ │ │ └── video-{id}/ │ │ └── resources/ │ │ └── resource-{id}-{filename} │ └── certificate/ │ ├── template-{id}-{filename} │ └── signature-{id}-{filename} └── profile/ └── user-{id}/ ├── avatar-{id}-{filename} └── certificate-{id}-{filename} ``` ### Path Generation The `getS3StoragePath` function generates standardized paths: ```typescript title="lib/aws/s3.ts" theme={null} export function getS3StoragePath( type: "video" | "thumbnail" | "document" | "avatar" | "certificate", userId: string, filename: string, additionalPath?: string, fileHash?: string, courseId?: string | number, lessonId?: string | number, resourceId?: string | number, fileId?: string | number ): string { const timestamp = Date.now() const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, "_") const hashPrefix = fileHash ? `${fileHash.substring(0, 8)}-` : "" const fileIdentifier = fileId ? `${fileId}` : timestamp switch (type) { case "video": if (courseId && lessonId) { return `courses/course-${courseId}/lessons/lesson-${lessonId}/video-${fileIdentifier}-${hashPrefix}${sanitizedFilename}` } if (courseId) { return `courses/course-${courseId}/preview-video-${fileIdentifier}-${hashPrefix}${sanitizedFilename}` } return `courses/temp-${userId}/videos/video-${fileIdentifier}-${hashPrefix}${sanitizedFilename}` case "thumbnail": if (courseId) { return `courses/course-${courseId}/thumbnail-${fileIdentifier}-${hashPrefix}${sanitizedFilename}` } return `courses/temp-${userId}/thumbnail-${fileIdentifier}-${hashPrefix}${sanitizedFilename}` // Additional cases... } } ``` The path structure supports file deduplication using optional hash prefixes and provides clear organization by course, lesson, and resource type. ## Upload Methods ### Direct Upload (Server-Side) ```typescript title="lib/aws/s3.ts" theme={null} export async function uploadFileToS3( file: Buffer, key: string, contentType: string ): Promise<{ key: string; url: string }> { const command = new PutObjectCommand({ Bucket: BUCKET_NAME, Key: key, Body: file, ContentType: contentType, }) await s3Client.send(command) const url = getPublicUrl(key) return { key, url } } ``` ### Presigned URL Upload (Client-Side) ```typescript title="lib/aws/s3.ts" theme={null} export async function getPresignedPutUrl( key: string, contentType: string, expiresIn: number = 3600 ): Promise { const command = new PutObjectCommand({ Bucket: BUCKET_NAME, Key: key, ContentType: contentType, }) return await getSignedUrl(s3Client, command, { expiresIn }) } ``` ```typescript title="app/api/upload/presigned-url/route.ts" theme={null} import { getPresignedPutUrl, getS3StoragePath } from '@/lib/aws/s3' export async function POST(request: Request) { const { filename, contentType, type, courseId, lessonId } = await request.json() const userId = "user-123" // Get from auth const key = getS3StoragePath( type, userId, filename, undefined, undefined, courseId, lessonId ) const presignedUrl = await getPresignedPutUrl(key, contentType) return Response.json({ presignedUrl, key }) } ``` ```typescript title="components/FileUploader.tsx" theme={null} async function uploadFile(file: File) { // 1. Request presigned URL const response = await fetch('/api/upload/presigned-url', { method: 'POST', body: JSON.stringify({ filename: file.name, contentType: file.type, type: 'video', courseId: 123, }), }) const { presignedUrl, key } = await response.json() // 2. Upload directly to S3 await fetch(presignedUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type, }, }) return key } ``` ## CDN Integration ### URL Transformation The system automatically transforms S3 URLs to CDN URLs: ```typescript title="lib/aws/s3.ts" theme={null} export function getPublicUrl(key: string, useCDN: boolean = false): string { const cleanKey = key.startsWith("/") ? key.slice(1) : key // Encode special characters const encodedKey = cleanKey.split("/").map(segment => { if (segment.includes(" ") || /[^a-zA-Z0-9._-]/.test(segment)) { return encodeURIComponent(segment) } return segment }).join("/") // Use CDN if configured const azureCDNUrl = process.env.AZURE_CDN_URL const useCDNEnv = process.env.USE_AZURE_CDN === 'true' if ((useCDN || useCDNEnv) && azureCDNUrl) { return `${azureCDNUrl}/${encodedKey}` } // Fallback to S3 const region = process.env.AWS_REGION || "us-east-1" return `https://${BUCKET_NAME}.s3.${region}.amazonaws.com/${encodedKey}` } ``` ### CDN Configuration ```bash title=".env.local" theme={null} AWS_CLOUDFRONT_DOMAIN=d123456.cloudfront.net ``` **CloudFront Setup:** 1. Create CloudFront distribution 2. Set S3 bucket as origin 3. Enable Origin Access Identity (OAI) 4. Configure cache behaviors: * Videos: Cache for 1 year * Images: Cache for 1 month * Documents: Cache for 1 week ```bash title=".env.local" theme={null} AZURE_CDN_URL=https://your-endpoint.azurefd.net USE_AZURE_CDN=true ``` **Azure Front Door Setup:** 1. Create Azure Front Door profile 2. Add S3 as backend origin 3. Configure routing rules 4. Set caching policies: * Video segments (.ts): Cache for 1 year * Manifests (.m3u8): Cache for 5 minutes * Thumbnails: Cache for 1 month ### Video URL with HLS For videos, the system automatically prefers HLS manifests: ```typescript title="lib/aws/s3.ts" theme={null} export function getHLSVideoUrl(originalVideoKey: string): string { const lastSlashIndex = originalVideoKey.lastIndexOf('/') const path = lastSlashIndex >= 0 ? originalVideoKey.substring(0, lastSlashIndex) : '' const filename = lastSlashIndex >= 0 ? originalVideoKey.substring(lastSlashIndex + 1) : originalVideoKey const baseName = filename.replace(/\.[^/.]+$/, '') const hlsKey = path ? `${path}/hls/${baseName}/${baseName}.m3u8` : `hls/${baseName}/${baseName}.m3u8` return getPublicUrl(hlsKey, true) } ``` ## File Validation ### File Type Validation ```typescript title="lib/aws/s3.ts" theme={null} export function isValidVideoFile(file: File): boolean { const validTypes = ["video/mp4", "video/webm", "video/ogg"] const validExtensions = ["mp4", "webm", "ogg"] const extension = file.name.split(".").pop()?.toLowerCase() return validTypes.includes(file.type) || (extension ? validExtensions.includes(extension) : false) } export function isValidImageFile(file: File): boolean { const validTypes = [ "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp", "image/svg+xml" ] const validExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg"] const extension = file.name.split(".").pop()?.toLowerCase() return validTypes.includes(file.type) || (extension ? validExtensions.includes(extension) : false) } export function isValidDocumentFile(file: File): boolean { const validTypes = [ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "text/plain", "application/zip" ] const validExtensions = ["pdf", "doc", "docx", "txt", "zip"] const extension = file.name.split(".").pop()?.toLowerCase() return validTypes.includes(file.type) || (extension ? validExtensions.includes(extension) : false) } ``` ### File Size Limits ```typescript title="lib/aws/s3.ts" theme={null} export function getMaxVideoSize(): number { return 2 * 1024 * 1024 * 1024 // 2GB } export function getMaxImageSize(): number { return 5 * 1024 * 1024 // 5MB } export function getMaxDocumentSize(): number { return 50 * 1024 * 1024 // 50MB } ``` ## File Deletion ### Delete Single File ```typescript title="lib/aws/s3.ts" theme={null} export async function deleteFileFromS3(key: string): Promise { const command = new DeleteObjectCommand({ Bucket: BUCKET_NAME, Key: key, }) await s3Client.send(command) } ``` ### Delete Video with HLS When deleting videos, also delete HLS transcoded files: ```typescript title="lib/aws/s3.ts" theme={null} export async function deleteVideoWithHLS(videoKey: string): Promise<{ deleted: number errors: string[] }> { const errors: string[] = [] let deleted = 0 // Delete original video try { await deleteFileFromS3(videoKey) deleted++ } catch (error: any) { if (!error.message?.includes('NoSuchKey')) { errors.push(`Failed to delete video: ${error.message}`) } } // Delete HLS folder try { const hlsDeletedCount = await deleteHLSFolder(videoKey) deleted += hlsDeletedCount } catch (error: any) { errors.push(`Failed to delete HLS folder: ${error.message}`) } return { deleted, errors } } ``` ## S3 Bucket Configuration ### CORS Policy ```json title="S3 Bucket CORS Configuration" theme={null} [ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "DELETE"], "AllowedOrigins": [ "http://localhost:3000", "https://yourdomain.com" ], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3000 } ] ``` ### Bucket Policy ```json title="S3 Bucket Policy" theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/*" } ] } ``` For production, restrict public access and use CloudFront/CDN with Origin Access Identity (OAI) for secure content delivery. ## Best Practices Upload large files directly from client to S3 using presigned URLs to avoid server bottlenecks and reduce bandwidth costs. Always use a CDN for production to: * Reduce latency with edge caching * Lower S3 data transfer costs * Improve video streaming performance Use file hashes to prevent duplicate uploads: ```typescript theme={null} const fileHash = await calculateFileHash(file) const key = getS3StoragePath(type, userId, filename, undefined, fileHash) ``` Configure S3 lifecycle rules to: * Delete temp files after 7 days * Transition old files to Glacier * Clean up incomplete multipart uploads ## Next Steps Learn about video transcoding with AWS MediaConvert Understand access control for file uploads # Video Processing & Streaming Source: https://docs.easelms.org/advanced/video-processing AWS MediaConvert for HLS transcoding and adaptive bitrate video streaming ## Overview EaseLMS uses AWS MediaConvert to transcode uploaded videos into HLS (HTTP Live Streaming) format with multiple bitrates for adaptive streaming. This provides optimal playback quality across different network conditions and devices. ## Video Processing Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Video Processing Flow │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. Upload MP4 → S3 │ │ 2. Trigger → MediaConvert Job │ │ 3. Transcode → Multiple Bitrates (1080p, 720p, 480p) │ │ 4. Output → HLS Manifest + Segments │ │ 5. Store → S3 (hls/ folder) │ │ 6. Deliver → CDN → Client Player │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ## HLS Output Structure MediaConvert generates HLS files in a structured format: ``` courses/course-18/lessons/lesson-42/ ├── video-123-original.mp4 # Original upload └── hls/ └── video-123/ # HLS folder ├── video-123.m3u8 # Master playlist ├── video-123_1080p.m3u8 # 1080p variant playlist ├── video-123_1080p00000.ts # 1080p segments ├── video-123_1080p00001.ts ├── video-123_720p.m3u8 # 720p variant playlist ├── video-123_720p00000.ts # 720p segments ├── video-123_720p00001.ts ├── video-123_480p.m3u8 # 480p variant playlist ├── video-123_480p00000.ts # 480p segments └── video-123_480p00001.ts ``` ## MediaConvert Client Setup ```typescript title="lib/aws/mediaconvert.ts" theme={null} import { MediaConvertClient, CreateJobCommand, GetJobCommand } from "@aws-sdk/client-mediaconvert" const BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME! const AWS_REGION = process.env.AWS_REGION || 'us-east-1' const mediaConvertClient = new MediaConvertClient({ region: AWS_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }) ``` ### Environment Variables ```bash title=".env.local" theme={null} AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key AWS_S3_BUCKET_NAME=your_bucket_name AWS_MEDIACONVERT_ROLE_ARN=arn:aws:iam::account:role/MediaConvertRole ``` The MediaConvert role must have permissions to read from and write to your S3 bucket. ## Creating MediaConvert Jobs ### Job Configuration The `createMediaConvertJob` function creates HLS transcoding jobs: ```typescript title="lib/aws/mediaconvert.ts" theme={null} export async function createMediaConvertJob(videoKey: string): Promise { const { hlsPath, baseName } = getHLSOutputPath(videoKey) const inputS3Url = `s3://${BUCKET_NAME}/${videoKey}` const outputS3Url = `s3://${BUCKET_NAME}/${hlsPath}/` const roleArn = process.env.AWS_MEDIACONVERT_ROLE_ARN if (!roleArn) { throw new Error("AWS_MEDIACONVERT_ROLE_ARN is required") } const jobSettings = { Role: roleArn, Settings: { Inputs: [{ FileInput: inputS3Url, VideoSelector: {}, AudioSelectors: { "Audio Selector 1": { DefaultSelection: "DEFAULT", }, }, }], OutputGroups: [{ Name: "HLS Output Group", OutputGroupSettings: { Type: "HLS_GROUP_SETTINGS", HlsGroupSettings: { SegmentLength: 10, Destination: outputS3Url, ManifestDurationFormat: "INTEGER", StreamInfResolution: "INCLUDE", }, DestinationSettings: { S3Settings: { AccessControl: { CannedAcl: "PUBLIC_READ", }, }, }, }, Outputs: [ // 1080p, 720p, 480p outputs (see below) ], }], }, } const command = new CreateJobCommand(jobSettings) const response = await mediaConvertClient.send(command) return response.Job!.Id! } ``` ### Output Configurations ```typescript theme={null} { NameModifier: "_1080p", VideoDescription: { CodecSettings: { Codec: "H_264", H264Settings: { RateControlMode: "QVBR", QvbrSettings: { QvbrQualityLevel: 7, }, MaxBitrate: 5000000, GopSize: 60, InterlaceMode: "PROGRESSIVE", CodecProfile: "HIGH", CodecLevel: "LEVEL_4", }, }, Width: 1920, Height: 1080, }, AudioDescriptions: [{ AudioSelectorName: "Audio Selector 1", CodecSettings: { Codec: "AAC", AacSettings: { Bitrate: 192000, CodingMode: "CODING_MODE_2_0", SampleRate: 48000, }, }, }], ContainerSettings: { Container: "M3U8", }, } ``` ```typescript theme={null} { NameModifier: "_720p", VideoDescription: { CodecSettings: { Codec: "H_264", H264Settings: { RateControlMode: "QVBR", QvbrSettings: { QvbrQualityLevel: 7, }, MaxBitrate: 3000000, GopSize: 60, CodecProfile: "HIGH", CodecLevel: "LEVEL_3_1", }, }, Width: 1280, Height: 720, }, AudioDescriptions: [{ AudioSelectorName: "Audio Selector 1", CodecSettings: { Codec: "AAC", AacSettings: { Bitrate: 128000, SampleRate: 48000, }, }, }], } ``` ```typescript theme={null} { NameModifier: "_480p", VideoDescription: { CodecSettings: { Codec: "H_264", H264Settings: { RateControlMode: "QVBR", QvbrSettings: { QvbrQualityLevel: 7, }, MaxBitrate: 1500000, GopSize: 60, CodecProfile: "MAIN", CodecLevel: "LEVEL_3_1", }, }, Width: 854, Height: 480, }, AudioDescriptions: [{ AudioSelectorName: "Audio Selector 1", CodecSettings: { Codec: "AAC", AacSettings: { Bitrate: 96000, SampleRate: 48000, }, }, }], } ``` ## HLS Path Generation The system generates HLS paths based on the original video key: ```typescript title="lib/aws/mediaconvert.ts" theme={null} function getHLSOutputPath(videoKey: string): { hlsPath: string baseName: string } { const lastSlashIndex = videoKey.lastIndexOf('/') const videoPath = lastSlashIndex >= 0 ? videoKey.substring(0, lastSlashIndex) : '' const filename = lastSlashIndex >= 0 ? videoKey.substring(lastSlashIndex + 1) : videoKey const baseName = filename.replace(/\.[^/.]+$/, '') const hlsPath = videoPath ? `${videoPath}/hls/${baseName}` : `hls/${baseName}` return { hlsPath, baseName } } ``` ### Example Path Transformation ```typescript theme={null} // Input const videoKey = "courses/course-18/preview-video-123.mp4" // Output const { hlsPath, baseName } = getHLSOutputPath(videoKey) // hlsPath: "courses/course-18/hls/preview-video-123" // baseName: "preview-video-123" // Master playlist URL const masterPlaylist = `${hlsPath}/${baseName}.m3u8` // "courses/course-18/hls/preview-video-123/preview-video-123.m3u8" ``` ## Job Status Monitoring ### Check Job Status ```typescript title="lib/aws/mediaconvert.ts" theme={null} export async function getMediaConvertJobStatus(jobId: string): Promise<{ status: JobStatus | string progress?: number errorMessage?: string }> { const command = new GetJobCommand({ Id: jobId }) const response = await mediaConvertClient.send(command) return { status: response.Job?.Status || "UNKNOWN", progress: response.Job?.JobPercentComplete, errorMessage: response.Job?.ErrorMessage, } } ``` ### Job Status Values Job has been submitted to MediaConvert queue. ```typescript theme={null} { status: "SUBMITTED", progress: 0 } ``` Job is currently being processed. ```typescript theme={null} { status: "PROGRESSING", progress: 45 } ``` Job completed successfully. HLS files are ready. ```typescript theme={null} { status: "COMPLETE", progress: 100 } ``` Job failed with error. ```typescript theme={null} { status: "ERROR", progress: 30, errorMessage: "Invalid input file format" } ``` ## Video Player Integration The frontend uses Media Chrome for HLS playback: ```typescript title="lib/hooks/useHLS.ts" theme={null} import Hls from 'hls.js' export function useHLS(videoUrl: string) { useEffect(() => { if (!videoRef.current) return // Check if HLS is supported if (Hls.isSupported()) { const hls = new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60, enableWorker: true, }) // Get HLS manifest URL const hlsUrl = getHLSVideoUrl(videoUrl) hls.loadSource(hlsUrl) hls.attachMedia(videoRef.current) hls.on(Hls.Events.MANIFEST_PARSED, () => { // Auto-play or wait for user interaction }) hls.on(Hls.Events.ERROR, (event, data) => { if (data.fatal) { // Fallback to original MP4 videoRef.current.src = videoUrl } }) return () => hls.destroy() } else if (videoRef.current.canPlayType('application/vnd.apple.mpegurl')) { // Native HLS support (Safari) videoRef.current.src = getHLSVideoUrl(videoUrl) } else { // Fallback to original video videoRef.current.src = videoUrl } }, [videoUrl]) } ``` ### Adaptive Bitrate Selection HLS.js automatically selects the optimal bitrate based on: * **Network bandwidth**: Measured during playback * **Screen size**: Higher quality for larger screens * **Buffer health**: Switches to lower quality if buffering occurs ## URL Generation for Playback ### Get HLS Manifest URL ```typescript title="lib/aws/s3.ts" theme={null} export function getHLSVideoUrl(originalVideoKey: string): string { const lastSlashIndex = originalVideoKey.lastIndexOf('/') const path = lastSlashIndex >= 0 ? originalVideoKey.substring(0, lastSlashIndex) : '' const filename = lastSlashIndex >= 0 ? originalVideoKey.substring(lastSlashIndex + 1) : originalVideoKey const baseName = filename.replace(/\.[^/.]+$/, '') const hlsKey = path ? `${path}/hls/${baseName}/${baseName}.m3u8` : `hls/${baseName}/${baseName}.m3u8` // Use CDN for delivery return getPublicUrl(hlsKey, true) } ``` ### Prefer HLS with MP4 Fallback ```typescript title="lib/aws/s3.ts" theme={null} export function getPreferredVideoUrl(originalVideoUrl: string): string { if (!originalVideoUrl) return originalVideoUrl // If already HLS, return as-is if (originalVideoUrl.includes('.m3u8')) { return transformToCDNUrl(originalVideoUrl, false) } // Return original URL - player will try HLS automatically return transformToCDNUrl(originalVideoUrl, false) } ``` ## Deleting Videos with HLS When deleting videos, clean up both original and HLS files: ```typescript title="lib/aws/s3.ts" theme={null} export async function deleteVideoWithHLS(videoKey: string): Promise<{ deleted: number errors: string[] }> { const errors: string[] = [] let deleted = 0 // Delete original video try { await deleteFileFromS3(videoKey) deleted++ } catch (error: any) { if (!error.message?.includes('NoSuchKey')) { errors.push(`Failed to delete video: ${error.message}`) } } // Delete HLS folder try { const hlsDeletedCount = await deleteHLSFolder(videoKey) deleted += hlsDeletedCount } catch (error: any) { errors.push(`Failed to delete HLS: ${error.message}`) } return { deleted, errors } } export async function deleteHLSFolder(videoKey: string): Promise { const hlsFolderPath = getHLSFolderPath(videoKey) let deletedCount = 0 const listCommand = new ListObjectsV2Command({ Bucket: BUCKET_NAME, Prefix: hlsFolderPath, }) const listResponse = await s3Client.send(listCommand) const objects = listResponse.Contents || [] for (const object of objects) { if (object.Key) { const deleteCommand = new DeleteObjectCommand({ Bucket: BUCKET_NAME, Key: object.Key, }) await s3Client.send(deleteCommand) deletedCount++ } } return deletedCount } ``` ## Complete Upload & Transcode Flow Upload MP4 to S3 using presigned URL: ```typescript theme={null} const videoKey = await uploadVideoToS3(file) // "courses/course-18/preview-video-123.mp4" ``` Create transcoding job: ```typescript theme={null} const jobId = await createMediaConvertJob(videoKey) // "1234567890123-abcdef" ``` Poll job status until complete: ```typescript theme={null} const interval = setInterval(async () => { const { status, progress } = await getMediaConvertJobStatus(jobId) if (status === "COMPLETE") { clearInterval(interval) // HLS files are ready } else if (status === "ERROR") { clearInterval(interval) // Handle error } }, 5000) ``` Generate HLS manifest URL: ```typescript theme={null} const hlsUrl = getHLSVideoUrl(videoKey) // "https://cdn.example.com/courses/course-18/hls/preview-video-123/preview-video-123.m3u8" ``` Use HLS.js or native player: ```typescript theme={null} ``` ## IAM Role Configuration The MediaConvert role requires these permissions: ```json title="MediaConvert IAM Role Policy" theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl" ], "Resource": "arn:aws:s3:::your-bucket-name/*" }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::your-bucket-name" } ] } ``` ## Best Practices QVBR provides better quality-to-file-size ratio than constant bitrate: ```typescript theme={null} RateControlMode: "QVBR", QvbrSettings: { QvbrQualityLevel: 7, // 1-10 scale } ``` 10-second segments balance startup time and seek performance: ```typescript theme={null} HlsGroupSettings: { SegmentLength: 10, } ``` Use accelerated transcoding for time-sensitive content (higher cost): ```typescript theme={null} AccelerationSettings: { Mode: "PREFERRED", // or "ENABLED" } ``` Save MediaConvert job IDs in your database to track processing status and handle failures. ## Troubleshooting Check that the MediaConvert role has S3 read/write permissions and the input file exists. Verify CORS is configured on S3 and the master playlist exists at the expected path. Increase QVBR quality level (7-9) and max bitrate for higher quality output. Enable acceleration mode or reduce output resolutions for faster transcoding. ## Next Steps Learn about S3 storage patterns and CDN integration Understand the complete system architecture # Authentication Source: https://docs.easelms.org/api/authentication Learn how to authenticate with the EaseLMS API using Supabase authentication tokens ## Overview EaseLMS uses Supabase authentication for API security. Most API endpoints require a valid Supabase authentication token in the request headers. ## Authentication Flow The API uses session-based authentication with Supabase: 1. User logs in via the `/api/auth/login` endpoint 2. Supabase creates a session and returns access tokens 3. Subsequent API requests include the session cookie 4. Middleware validates the session on protected routes ## User Types EaseLMS supports three user types: * **`user`** - Learners who can enroll in and take courses * **`instructor`** - Instructors who can create and manage courses * **`admin`** - Administrators with full system access ## Login ### Endpoint ``` POST /api/auth/login ``` ### Request Body User's email address User's password Type of user attempting to login: `user`, `instructor`, or `admin` ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "learner@example.com", "password": "secure_password", "userType": "user" }' ``` ### Success Response The authenticated user object from Supabase User's unique identifier (UUID) User's email address Timestamp when user was created The Supabase session object containing authentication tokens JWT access token for API requests Token used to refresh the access token Unix timestamp when the session expires ### Example Success Response ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "learner@example.com", "created_at": "2024-01-15T10:30:00Z" }, "session": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "v1.refresh_token_here...", "expires_at": 1705328400 } } ``` ### Error Responses #### Invalid Credentials ```json theme={null} { "error": "Invalid login credentials" } ``` Status: `401 Unauthorized` #### Wrong User Type If a learner tries to login through the admin portal: ```json theme={null} { "error": "Learner accounts cannot login through admin portal. Please use the learner login page." } ``` Status: `403 Forbidden` If an admin tries to login through the learner portal: ```json theme={null} { "error": "Admin accounts cannot login through learner portal. Please use the admin login page." } ``` Status: `403 Forbidden` Instructors can login through the admin portal and will be granted access. ## Making Authenticated Requests ### Using Session Cookies After successful login, Supabase automatically sets session cookies. Include these cookies in subsequent requests: ```bash theme={null} curl -X GET https://your-domain.com/api/enrollments \ -H "Cookie: sb-access-token=...; sb-refresh-token=..." \ -H "Content-Type: application/json" ``` ### Using Authorization Header Alternatively, you can use the access token in the Authorization header: ```bash theme={null} curl -X GET https://your-domain.com/api/enrollments \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H "Content-Type: application/json" ``` ## Checking Authentication Status Protected endpoints will return `401 Unauthorized` if the session is invalid or expired: ```json theme={null} { "error": "Unauthorized" } ``` ## User Permissions Different endpoints require different permission levels: ### Public Endpoints These endpoints don't require authentication: * `GET /api/courses` (published courses only) * `GET /api/courses/[id]` (published courses only) ### Authenticated User Endpoints These endpoints require any authenticated user: * `GET /api/enrollments` (returns user's own enrollments) * `POST /api/enrollments` (self-enrollment) * `GET /api/progress` * `POST /api/progress` * `GET /api/profile` ### Admin/Instructor Endpoints These endpoints require admin or instructor privileges: * `POST /api/courses` (admin only) * `GET /api/courses?all=true` (all courses including drafts) * `GET /api/users` (admin only) * `POST /api/users` (admin only) * `GET /api/admin/stats` (admin only) ### Permission Denied Response If a user lacks required permissions: ```json theme={null} { "error": "Forbidden" } ``` Status: `403 Forbidden` ## Signup ### Endpoint ``` POST /api/auth/signup ``` ### Request Body New user's email address New user's password New user's full name Type of user account: `user`, `instructor`, or `admin` ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/auth/signup \ -H "Content-Type: application/json" \ -d '{ "email": "newlearner@example.com", "password": "secure_password123", "name": "John Doe", "userType": "user" }' ``` ## Logout ### Endpoint ``` POST /api/auth/logout ``` ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/auth/logout \ -H "Cookie: sb-access-token=...; sb-refresh-token=..." ``` ## Password Reset ### Request Password Reset ``` POST /api/auth/reset-password ``` ### Change Password ``` POST /api/auth/change-password ``` ## Row Level Security (RLS) EaseLMS uses Supabase Row Level Security to protect data at the database level. The API includes two types of Supabase clients: ### Regular Client Used for user-scoped operations. RLS policies apply: * Users can only access their own enrollments * Users can only update their own progress * Users can only view their own profile ### Service Role Client Used for admin operations and bypasses RLS: * Admin user management * Cross-user enrollment operations * System-wide statistics The service role key should never be exposed to the client. It's only used server-side in API routes. ## Security Best Practices Never send authentication credentials over unencrypted HTTP connections. If storing tokens on the client, use secure, httpOnly cookies or secure storage mechanisms. The API validates user types on login to prevent unauthorized access to admin/instructor features. Implement token refresh logic to handle expired sessions gracefully. Keep `SUPABASE_SERVICE_ROLE_KEY` private and only use it server-side. ## Next Steps Learn about API structure and response formats Start working with the Courses API # Certificate API Source: https://docs.easelms.org/api/certificates API endpoints for issuing and downloading course completion certificates ## List Certificates Retrieves all certificates for the authenticated user or a specific user (admin only). ### Query Parameters Admin only: User UUID to retrieve certificates for ### Response Array of certificate objects Certificate UUID Unique certificate number (e.g., "CERT-1234") Course ID Course title Issue timestamp (ISO 8601) Certificate type: "completion", "participation", "achievement" ### Example Request ```bash theme={null} curl https://your-domain.com/api/certificates ``` ### Example Response ```json theme={null} { "certificates": [ { "id": "uuid-here", "certificateNumber": "CERT-1234567890-ABC123", "courseId": 42, "courseTitle": "Introduction to Programming", "issuedAt": "2024-01-15T10:30:00Z", "certificateType": "completion" } ] } ``` *** ## Issue Certificate Issues a new certificate for a completed course. Requires course completion and certificate enablement. ### Request Body Course ID to issue certificate for ### Response Certificate UUID Unique certificate number Success or informational message ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/certificates \ -H "Content-Type: application/json" \ -d '{ "courseId": 42 }' ``` ### Example Response ```json theme={null} { "certificate": { "id": "uuid-here", "certificateNumber": "CERT-1234567890-ABC123" }, "message": "Certificate created successfully" } ``` ### Validation Before issuing a certificate, the API validates: 1. ✓ User has completed the course (status = "completed") 2. ✓ Certificates are enabled for the course (`certificate_enabled = true`) 3. ✓ Certificate doesn't already exist for this user-course combination *** ## Download Certificate Generates and downloads a PDF certificate. The PDF is generated on-demand and uploaded to S3 for future access. ### Path Parameters Certificate UUID ### Response Returns a PDF file with: * **Content-Type**: `application/pdf` * **Content-Disposition**: `attachment; filename="certificate-{number}.pdf"` ### Example Request ```bash theme={null} curl https://your-domain.com/api/certificates/uuid-here/download \ -o certificate.pdf ``` ### Certificate Generation The API: 1. Validates user access (owner or admin) 2. Checks if PDF already exists in S3 3. If not, generates PDF with: * Platform logo and name (from brand settings) * Learner name * Course title * Certificate number * Issue date * Certificate type (completion/participation/achievement) * Digital signature (if configured) 4. Uploads PDF to S3 5. Saves PDF URL to database (`certificate_url`) 6. Returns PDF file *** ## Certificate Types Three certificate types are supported: | Type | Description | Use Case | | ----------------- | ----------------------------- | ----------------------------------------------- | | **completion** | Course completion certificate | User completed all required lessons and quizzes | | **participation** | Participation certificate | User attended/participated in the course | | **achievement** | Achievement certificate | User achieved specific milestones or scores | *** ## Certificate Configuration Certificates are configured at the course level with these fields: Enable/disable certificates for this course Certificate type: "completion", "participation", "achievement" Custom template selection Custom certificate title (e.g., "Certificate of Excellence") Custom description text URL to signature image Signatory name Signatory title (e.g., "CEO", "Lead Instructor") Additional custom text *** ## Brand Integration Certificates automatically include: * **Platform Logo**: Fetched from brand settings (`logoBlack` for white background) * **Organization Name**: From brand settings (`platformName`) * **Fallback**: Uses default logo and "EaseLMS" if brand settings unavailable *** ## Certificate Notifications When a certificate is issued, the API automatically: 1. Sends email notification to the user 2. Includes download link in the email 3. Email contains certificate number and course details *** ## Certificate Number Format Certificate numbers are generated as: ``` CERT-{timestamp}-{random} ``` Example: `CERT-1705320600000-A1B2C3D4E` This ensures: * Uniqueness (timestamp + random) * Sortability (timestamp-based) * Easy identification (CERT prefix) *** ## PDF Storage Generated PDFs are: 1. Stored in S3 with path: `certificates/{userId}/certificate-{number}.pdf` 2. URL saved to `certificate_url` field 3. Reused for future downloads (regenerated only if missing) 4. Accessible via direct S3 URL or download endpoint *** ## Error Codes | Status Code | Description | | ----------- | ----------------------------------------------------------- | | 200 | Success - Returns PDF file | | 400 | Bad Request - Course not completed or certificates disabled | | 401 | Unauthorized - Authentication required | | 403 | Forbidden - Not authorized to download this certificate | | 404 | Not Found - Certificate not found | | 500 | Internal Server Error - PDF generation failed | # Course API Source: https://docs.easelms.org/api/courses API endpoints for managing courses, lessons, and course content ## List Courses Retrieves a list of published courses or all courses (admin/instructor only). ### Query Parameters Returns only the 4 most recently published courses when set to `true` Comma-separated list of course IDs to filter by (e.g., "1,2,3") Admin/instructor only: Returns all courses including unpublished drafts when set to `true` ### Response Array of course objects Unique course identifier Course title Course description Course thumbnail image URL Course price in default currency Total number of enrolled students (calculated dynamically) Total course duration in hours Total course duration in minutes Array of lesson objects with id, video\_url, text\_content, and estimated\_duration Course settings including enrollment mode, pricing, and certificate configuration ### Example Request ```bash theme={null} curl https://your-domain.com/api/courses?recommended=true ``` ### Example Response ```json theme={null} { "courses": [ { "id": 1, "title": "Introduction to Programming", "description": "Learn the basics of programming", "image": "https://example.com/image.jpg", "price": 49.99, "enrolledStudents": 125, "totalHours": 12.5, "totalDurationMinutes": 750, "lessons": [...], "settings": { "enrollment": { "enrollmentMode": "paid", "price": 49.99 } } } ] } ``` *** ## Create Course Creates a new course. Requires admin authentication. ### Request Body Course title Course description Course thumbnail image URL Course price Enrollment mode: "free" or "paid" ### Response The created course object ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/courses \ -H "Content-Type: application/json" \ -d '{ "title": "Advanced JavaScript", "description": "Master JavaScript concepts", "price": 99.99, "enrollment_mode": "paid" }' ``` *** ## Get Course Details Retrieves detailed information about a specific course including lessons, quizzes, instructors, and prerequisites. ### Path Parameters Course ID or slug (e.g., "123" or "course-title-123") ### Response Detailed course object with lessons, creator, instructors, prerequisites Ordered array of lesson objects with quiz questions, resources, and content Profile information of the course creator Array of instructor profiles assigned to the course Array of prerequisite courses that must be completed first Current enrollment count ### Example Request ```bash theme={null} curl https://your-domain.com/api/courses/introduction-to-programming-123 ``` *** ## Delete Course Deletes a course and all related data (lessons, quizzes, enrollments, progress). Requires admin authentication. Payment and certificate records are preserved for historical purposes. ### Path Parameters Course ID or slug ### Response Success confirmation message ### Example Request ```bash theme={null} curl -X DELETE https://your-domain.com/api/courses/123 ``` *** ## Save/Update Course Draft Saves or updates a course draft including all lessons, quizzes, resources, prerequisites, and instructors. ### Request Body Course ID for updates, or "new" for creating a new course Whether the course should be published (visible to students) Complete course data including basicInfo, lessons, settings Title, description, thumbnail, previewVideo, requirements, whoIsThisFor, price Array of lesson objects with title, type, content, quizzes, and resources Enrollment, certificate, prerequisites, instructor, and quiz settings ### Response Updated course object Course ID (useful for newly created courses) *** ## Get Quiz Attempts Retrieves the latest quiz attempt for a specific lesson. ### Path Parameters Course ID ### Query Parameters Lesson ID ### Response Latest quiz attempt object with question\_order and answer\_orders for shuffled quizzes Current attempt number (0 if no attempts exist) *** ## Get Quiz Results Retrieves all quiz results for the authenticated user in a course. ### Path Parameters Course ID ### Response Array of quiz result objects Results grouped by lesson ID *** ## Submit Quiz Results Submits quiz answers and calculates results. Handles quiz retakes and shuffle logic. ### Path Parameters Course ID ### Request Body Lesson ID Array of answer objects with questionId and userAnswer ### Response Success message Saved quiz result records Total number of questions Number of correct answers Maximum possible points Points earned by the user Percentage score (0-100) *** ## Error Codes | Status Code | Description | | ----------- | -------------------------------------- | | 200 | Success | | 400 | Bad Request - Invalid parameters | | 401 | Unauthorized - Authentication required | | 403 | Forbidden - Insufficient permissions | | 404 | Not Found - Course not found | | 409 | Conflict - Already enrolled | | 500 | Internal Server Error | # Enrollment API Source: https://docs.easelms.org/api/enrollments API endpoints for managing course enrollments and checking enrollment status ## List Enrollments Retrieves all enrollments for the authenticated user. ### Response Array of enrollment objects Unique enrollment identifier User UUID Course ID Enrollment status: "active", "completed", "suspended" Completion progress (0-100) Enrollment timestamp (ISO 8601) Completion timestamp (ISO 8601, null if not completed) Embedded course object with course details ### Example Request ```bash theme={null} curl https://your-domain.com/api/enrollments ``` ### Example Response ```json theme={null} { "enrollments": [ { "id": 1, "user_id": "uuid-here", "course_id": 42, "status": "active", "progress": 65, "enrolled_at": "2024-01-15T10:30:00Z", "completed_at": null, "courses": { "id": 42, "title": "Introduction to Programming", "image": "https://example.com/image.jpg" } } ] } ``` *** ## Create Enrollment Enrolls a user in a course. Checks prerequisites and updates enrollment count. ### Request Body Course ID to enroll in Admin only: Enroll another user by their UUID Admin only: Skip prerequisite checks (default: false) ### Response Created enrollment object ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/enrollments \ -H "Content-Type: application/json" \ -d '{ "courseId": 42 }' ``` ### Example Response ```json theme={null} { "enrollment": { "id": 1, "user_id": "uuid-here", "course_id": 42, "status": "active", "progress": 0, "enrolled_at": "2024-01-15T10:30:00Z" } } ``` *** ## Update Enrollment Status Updates the status of an enrollment (e.g., mark as completed). ### Request Body Course ID New status: "active", "completed", "suspended" ### Response Updated enrollment object ### Example Request ```bash theme={null} curl -X PATCH https://your-domain.com/api/enrollments \ -H "Content-Type: application/json" \ -d '{ "courseId": 42, "status": "completed" }' ``` ### Notes * Setting status to "completed" automatically sets `completed_at` timestamp and progress to 100% * Triggers email notifications for course completion * Sends admin notification about the completion *** ## Prerequisite Validation When enrolling in a course with prerequisites, the API validates: 1. All prerequisite courses must be completed 2. Completion is verified by `completed_at` timestamp being set 3. In-progress enrollments are not sufficient ### Prerequisite Error Response ```json theme={null} { "error": "Prerequisites not met", "missingPrerequisites": [ { "id": 10, "title": "Introduction to Programming", "status": "not_started" }, { "id": 15, "title": "Basic JavaScript", "status": "in_progress" } ] } ``` *** ## Enrollment Notifications The API automatically triggers email notifications: * **On Enrollment**: Welcome email with course access details * **On Completion**: Congratulations email with certificate link (if enabled) * **Admin Notification**: Notifies admin when a user completes a course *** ## Admin Enrollment Admins can enroll other users by providing the `userId` parameter: ```bash theme={null} curl -X POST https://your-domain.com/api/enrollments \ -H "Content-Type: application/json" \ -d '{ "courseId": 42, "userId": "target-user-uuid", "bypassPrerequisites": true }' ``` *** ## Error Codes | Status Code | Description | | ----------- | --------------------------------------------------------- | | 200 | Success | | 400 | Bad Request - Prerequisites not met or invalid parameters | | 401 | Unauthorized - Authentication required | | 403 | Forbidden - Admin access required | | 404 | Not Found - Enrollment or course not found | | 409 | Conflict - User already enrolled | | 500 | Internal Server Error | # Lesson API Source: https://docs.easelms.org/api/lessons API endpoints for accessing lesson content, quizzes, and resources ## Get Lesson Details Retrieves detailed information about a specific lesson including content, quizzes, and resources. Requires enrollment in the parent course. ### Path Parameters Lesson ID ### Response Detailed lesson object Unique lesson identifier Lesson title Lesson type: "video", "text", or "quiz" Video URL for video lessons (stored in dedicated column) HTML or text content for text lessons Estimated duration in minutes Whether this lesson is required for course completion Position of the lesson in the course Parent course ID Array of downloadable resource objects Array of quiz question objects from the quiz\_questions table Parent course basic information (id, title, settings) ### Example Request ```bash theme={null} curl https://your-domain.com/api/lessons/42 ``` ### Example Response ```json theme={null} { "lesson": { "id": 42, "title": "Introduction to Variables", "type": "video", "video_url": "https://example.com/video.mp4", "text_content": null, "estimated_duration": 15, "is_required": true, "order_index": 0, "course_id": 1, "resources": [ { "id": "1", "title": "Lesson Notes", "type": "pdf", "url": "https://example.com/notes.pdf", "file_size": 1024000 } ], "quiz_questions": [ { "id": "10", "question_type": "multiple-choice", "question_text": "What is a variable?", "question_data": { "options": ["A container for data", "A function", "A loop"], "correctOption": 0 }, "points": 1, "order_index": 0 } ], "courses": { "id": 1, "title": "Introduction to Programming", "settings": { "requiresSequentialProgress": false } } } } ``` *** ## Lesson Access Control Access to lesson content is controlled by enrollment status: * **Regular Users**: Must be enrolled in the course to access any lesson * **Admin/Instructor**: Can access all lessons without enrollment The API automatically checks enrollment status and returns a 403 Forbidden error if the user is not enrolled. *** ## Lesson Content Types Lessons support three main content types: ### Video Lessons Video lessons have a `video_url` field pointing to the video file. The URL may be transformed to use Azure Front Door CDN if enabled. ```json theme={null} { "type": "video", "video_url": "https://cdn.example.com/videos/lesson-42.mp4", "estimated_duration": 15 } ``` ### Text Lessons Text lessons contain HTML or markdown content in the `text_content` field. ```json theme={null} { "type": "text", "text_content": "

Introduction

Welcome to this lesson...

", "estimated_duration": 10 } ``` ### Quiz Lessons Quiz lessons contain questions from the normalized `quiz_questions` table. Questions support multiple types: * **multiple-choice**: Single correct answer from options * **true-false**: Boolean answer * **fill-blank**: Text input with correct answers * **short-answer**: Keyword-based evaluation * **essay**: Free-form text (manual grading) * **matching**: Match items from two lists *** ## Quiz Question Structure Quiz questions are stored in the `quiz_questions` table with the following structure: Unique question identifier Parent lesson ID Question type: "multiple-choice", "true-false", "fill-blank", "short-answer", "essay", "matching" The question text Type-specific data (options, correct answers, etc.) Points awarded for correct answer (default: 1) Explanation shown after answering Difficulty level: "easy", "medium", "hard" Time limit in seconds (optional) Optional image for the question Question order in the quiz *** ## Resources Lessons can include downloadable resources linked through the `lesson_resources` junction table: ```json theme={null} { "id": "1", "title": "Lesson Notes", "description": "Detailed notes for this lesson", "type": "pdf", "url": "https://s3.amazonaws.com/bucket/notes.pdf", "file_size": 1024000, "mime_type": "application/pdf", "download_count": 42 } ``` *** ## Error Codes | Status Code | Description | | ----------- | -------------------------------------- | | 200 | Success | | 401 | Unauthorized - Authentication required | | 403 | Forbidden - Not enrolled in course | | 404 | Not Found - Lesson not found | | 500 | Internal Server Error | # API Overview Source: https://docs.easelms.org/api/overview Learn about the EaseLMS API, including base URL, versioning, response formats, and common patterns ## Introduction The EaseLMS API is a RESTful API built with Next.js API routes that provides programmatic access to your learning management system. All API endpoints return JSON responses and use standard HTTP response codes. ## Base URL All API requests should be made to: ``` https://your-domain.com/api ``` For local development: ``` http://localhost:3000/api ``` ## Versioning The current API does not use versioning in the URL path. All endpoints are accessed directly under `/api/` route. ## Request Format ### HTTP Methods The API uses standard HTTP methods: * `GET` - Retrieve resources * `POST` - Create new resources * `PATCH` - Update existing resources * `PUT` - Replace existing resources * `DELETE` - Remove resources ### Content Type All requests with a body should use `application/json` content type: ```bash theme={null} Content-Type: application/json ``` ## Response Format ### Success Responses Successful responses return a `200` status code with a JSON object: ```json theme={null} { "courses": [ { "id": 1, "title": "Introduction to Programming", "description": "Learn the basics of programming", "is_published": true, "price": 49.99, "enrolledStudents": 150 } ] } ``` ### Error Responses Error responses include an error message and appropriate HTTP status code: ```json theme={null} { "error": "Unauthorized" } ``` ### Common HTTP Status Codes | Status Code | Meaning | | ----------- | --------------------------------------------- | | `200` | OK - Request succeeded | | `201` | Created - Resource successfully created | | `400` | Bad Request - Invalid parameters | | `401` | Unauthorized - Authentication required | | `403` | Forbidden - Insufficient permissions | | `404` | Not Found - Resource doesn't exist | | `409` | Conflict - Resource already exists | | `500` | Internal Server Error - Server error occurred | ## CORS The API supports Cross-Origin Resource Sharing (CORS) for API routes. CORS headers are automatically added: * **Allowed Origin**: Configured via `NEXT_PUBLIC_WEBSITE_URL` environment variable (or `*` for development) * **Allowed Methods**: `GET, POST, PUT, DELETE, OPTIONS` * **Allowed Headers**: `Content-Type, Authorization` * **Max Age**: `86400` seconds (24 hours) ## Rate Limiting Currently, the API does not implement rate limiting. This may be added in future versions. ## Common Patterns ### Pagination Most list endpoints support query parameters for filtering: ```bash theme={null} GET /api/courses?recommended=true ``` ### Filtering by IDs Multiple resources can be retrieved by ID using comma-separated values: ```bash theme={null} GET /api/courses?ids=1,2,3 ``` ### Service Role Access Public endpoints (like course listings) can be accessed without authentication. Protected endpoints require valid authentication tokens. ## Environment Variables The API requires the following environment variables: Your Supabase project URL Your Supabase anonymous/public API key Your Supabase service role key for bypassing Row Level Security (RLS) Your website URL for CORS configuration (optional, defaults to `*` in development) Your application URL for internal API calls (optional, defaults to `http://localhost:3000`) ## Next Steps Learn how to authenticate API requests Work with courses and lessons Manage users and profiles Handle course enrollments # Payment API Source: https://docs.easelms.org/api/payments API endpoints for processing course payments with Stripe and Flutterwave ## Create Payment Intent Initializes a payment session for course enrollment. Automatically selects the appropriate payment gateway (Stripe or Flutterwave) based on user currency. ### Request Body Course ID to purchase Enrollment mode (informational) Course title (used for payment description) Redirect context: "course-detail" or "courses-list" ### Response Response varies by payment gateway: #### Stripe Response Stripe Checkout session URL "stripe" Payment amount in user's currency User's preferred currency Original course price in platform currency Platform's default currency #### Flutterwave Response Flutterwave payment page URL "flutterwave" Transaction reference ID Payment amount Payment currency ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/payments/create-intent \ -H "Content-Type: application/json" \ -d '{ "courseId": 42, "enrollmentMode": "paid", "courseTitle": "Advanced JavaScript", "referrer": "course-detail" }' ``` ### Example Response (Stripe) ```json theme={null} { "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_...", "gateway": "stripe", "amount": 99.99, "currency": "USD", "originalAmount": 99.99, "originalCurrency": "USD" } ``` ### Example Response (Flutterwave) ```json theme={null} { "paymentLink": "https://checkout.flutterwave.com/v3/...", "gateway": "flutterwave", "txRef": "tx_1234567890_uuid_42", "amount": 41650.00, "currency": "NGN", "originalAmount": 99.99, "originalCurrency": "USD" } ``` *** ## Record Payment Records a completed payment in the database. Called after successful payment via gateway callback. ### Request Body Course ID Payment amount Payment gateway: "stripe" or "flutterwave" ### Response Payment recording status Created payment record Payment ID User UUID Course ID Course price in platform currency Platform currency Amount charged to user User's payment currency Exchange rate applied Payment gateway used Payment status ("completed") Unique transaction identifier Completion timestamp ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/payments/record-payment \ -H "Content-Type: application/json" \ -d '{ "courseId": 42, "amount": 99.99, "gateway": "stripe" }' ``` *** ## Payment Gateway Selection The API automatically selects the payment gateway based on the user's currency preference: | Currency | Gateway | Notes | | --------------------------- | ----------- | ----------------------------- | | NGN (Nigerian Naira) | Flutterwave | Optimized for African markets | | All others (USD, EUR, etc.) | Stripe | Global payment processing | *** ## Currency Conversion Course prices are stored in the platform's default currency (configurable in `platform_settings`). The API: 1. Fetches the course price in platform currency 2. Gets the user's preferred currency from their profile 3. Converts the amount using real-time exchange rates 4. Creates a payment session in the user's currency 5. Stores both original and payment amounts for reporting ### Currency Fields * **original\_amount**: Course price in platform currency * **original\_currency**: Platform's default currency (e.g., "USD") * **payment\_amount**: Amount charged to user (after conversion) * **payment\_currency**: User's payment currency * **exchange\_rate**: Rate used for conversion (amountUSD / originalAmount) *** ## Payment Callbacks ### Stripe Callback Handles Stripe Checkout session completion. **Query Parameters:** * `success`: "true" for successful payment * `courseId`: Course ID * `referrer`: Redirect context ### Flutterwave Callback Handles Flutterwave payment completion. **Query Parameters:** * `status`: Payment status * `tx_ref`: Transaction reference * `transaction_id`: Flutterwave transaction ID * `courseId`: Course ID * `referrer`: Redirect context *** ## Payment Webhooks ### Stripe Webhook Receives and processes Stripe webhook events for payment confirmations and updates. *** ## Payment Flow 1. **User initiates payment**: POST to `/api/payments/create-intent` 2. **API creates payment session**: * Converts price to user's currency * Selects appropriate gateway (Stripe or Flutterwave) * Returns checkout/payment URL 3. **User completes payment**: On gateway-hosted page 4. **Gateway redirects to callback**: `/api/payments/callback/{gateway}` 5. **Callback records payment**: Calls `/api/payments/record-payment` 6. **User enrolled automatically**: Callback creates enrollment record 7. **User redirected**: To course page or courses list *** ## Error Codes | Status Code | Description | | ----------- | --------------------------------------------- | | 200 | Success | | 400 | Bad Request - Invalid course ID or parameters | | 401 | Unauthorized - Authentication required | | 404 | Not Found - Course not found | | 500 | Internal Server Error - Payment gateway error | # Progress API Source: https://docs.easelms.org/api/progress API endpoints for tracking lesson and course progress ## Get Progress Retrieves progress records for the authenticated user. ### Query Parameters Filter progress by specific course ID ### Response Array of progress objects Progress record ID User UUID Course ID Lesson ID Whether the lesson is completed Video playback progress in seconds Last playback position in seconds Completion timestamp (ISO 8601, null if not completed) Record creation timestamp Last update timestamp ### Example Request ```bash theme={null} curl https://your-domain.com/api/progress?courseId=42 ``` ### Example Response ```json theme={null} { "progress": [ { "id": 1, "user_id": "uuid-here", "course_id": 42, "lesson_id": 1, "completed": true, "video_progress": 0, "last_position": 0, "completed_at": "2024-01-15T10:30:00Z", "created_at": "2024-01-15T09:00:00Z", "updated_at": "2024-01-15T10:30:00Z" }, { "id": 2, "user_id": "uuid-here", "course_id": 42, "lesson_id": 2, "completed": false, "video_progress": 145, "last_position": 145, "completed_at": null, "created_at": "2024-01-15T10:35:00Z", "updated_at": "2024-01-15T10:42:00Z" } ] } ``` *** ## Update Progress Updates or creates a progress record for a lesson. This endpoint is used to: * Track video playback position * Mark lessons as completed * Update overall course progress ### Request Body Course ID Lesson ID Whether the lesson is completed Current video playback position in seconds Last known playback position in seconds ### Response Updated or created progress object ### Example Request ```bash theme={null} curl -X POST https://your-domain.com/api/progress \ -H "Content-Type: application/json" \ -d '{ "course_id": 42, "lesson_id": 2, "completed": false, "video_progress": 180, "last_position": 180 }' ``` ### Example Response ```json theme={null} { "progress": { "id": 2, "user_id": "uuid-here", "course_id": 42, "lesson_id": 2, "completed": false, "video_progress": 180, "last_position": 180, "completed_at": null, "created_at": "2024-01-15T10:35:00Z", "updated_at": "2024-01-15T10:50:00Z" } } ``` *** ## Progress Tracking Logic The API uses an upsert pattern: 1. **Check for existing record**: Query by `user_id` + `lesson_id` 2. **If exists**: Update the record with new values 3. **If not exists**: Insert a new progress record 4. **Return**: The updated or created record This ensures: * No duplicate progress records * Seamless updates without checking existence first * Automatic progress resumption *** ## Video Progress Tracking For video lessons: * **video\_progress**: Current playback position (updated frequently) * **last\_position**: Last saved position (for resume functionality) * Allows users to resume from where they left off * Progress is saved every few seconds during playback ### Example: Video Resume Flow 1. User watches video to 180 seconds 2. Client sends: `{ video_progress: 180, last_position: 180 }` 3. User closes browser 4. User returns to lesson 5. Client fetches progress: GET `/api/progress?courseId=42` 6. Client resumes video at `last_position` (180 seconds) *** ## Lesson Completion To mark a lesson as completed: ```bash theme={null} curl -X POST https://your-domain.com/api/progress \ -H "Content-Type: application/json" \ -d '{ "course_id": 42, "lesson_id": 2, "completed": true }' ``` The API automatically: * Sets `completed_at` timestamp * Updates enrollment progress percentage * Checks if course is fully completed * Triggers certificate generation (if enabled) *** ## Course Completion Calculation Course progress is calculated as: ``` progress = (completed_lessons / total_required_lessons) * 100 ``` Only lessons with `is_required = true` are counted. When all required lessons are completed: 1. Enrollment status → "completed" 2. Enrollment `completed_at` → current timestamp 3. Certificate generation triggered (if enabled) 4. Completion email sent *** ## Progress States | State | Conditions | Description | | --------------- | ----------------------------------------- | --------------------------------- | | **Not Started** | No progress record exists | User hasn't accessed the lesson | | **In Progress** | `completed = false`, `video_progress > 0` | User started but hasn't completed | | **Completed** | `completed = true`, `completed_at` set | User finished the lesson | *** ## Sequential Progress Some courses require sequential progress (`requires_sequential_progress = true`): * Users must complete lessons in order * Can't skip ahead to later lessons * Progress API doesn't enforce this (enforced in frontend) * Backend only tracks completion state *** ## Get Learner Progress (Admin) Admins can view any learner's progress. ### Path Parameters Learner UUID ### Query Parameters Filter by specific course *** ## Progress Analytics Progress data can be used for: * **Course completion rates**: % of enrolled users who complete * **Lesson difficulty**: Lessons with low completion rates * **Drop-off points**: Where users stop progressing * **Engagement metrics**: Average video watch time * **Time to completion**: Days from enrollment to completion *** ## Error Codes | Status Code | Description | | ----------- | -------------------------------------- | | 200 | Success | | 400 | Bad Request - Missing required fields | | 401 | Unauthorized - Authentication required | | 500 | Internal Server Error | # User/Profile API Source: https://docs.easelms.org/api/users API endpoints for managing user profiles and account settings ## Get Profile Retrieves the authenticated user's profile. Automatically creates a profile if one doesn't exist. ### Response User profile object User UUID User email address User display name User role: "user" (learner), "instructor", or "admin" User biography/description Profile image URL Preferred currency (ISO 4217 code, e.g., "USD", "NGN") Profile creation timestamp (ISO 8601) Last update timestamp (ISO 8601) ### Example Request ```bash theme={null} curl https://your-domain.com/api/profile ``` ### Example Response ```json theme={null} { "profile": { "id": "uuid-here", "email": "user@example.com", "name": "John Doe", "user_type": "user", "bio": "Aspiring developer", "profile_image": "https://example.com/avatar.jpg", "currency": "USD", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-15T10:30:00Z" } } ``` *** ## Update Profile Updates the authenticated user's profile information. ### Request Body User display name User biography/description Profile image URL Preferred currency (ISO 4217 code) ### Response Updated profile object ### Example Request ```bash theme={null} curl -X PUT https://your-domain.com/api/profile \ -H "Content-Type: application/json" \ -d '{ "name": "Jane Doe", "bio": "Full-stack developer", "currency": "EUR" }' ``` *** ## Delete Account Deletes the authenticated user's account and all associated data. ### Response Success confirmation message ### Example Request ```bash theme={null} curl -X DELETE https://your-domain.com/api/profile ``` ### Notes This action is irreversible. It deletes: * User authentication account * Profile data * All enrollments * All progress records * All quiz results *** ## List Learners (Admin/Instructor) Retrieves a list of all learners (users with user\_type = "user"). Requires admin or instructor authentication. ### Query Parameters Search by name or email Filter by enrollment status: "all", "enrolled", "not-enrolled" ### Response Array of learner objects User UUID User name User email Profile image URL Array of enrolled course IDs Array of completed course IDs Object mapping course IDs to progress percentages ### Example Request ```bash theme={null} curl https://your-domain.com/api/learners?search=john&enrollmentFilter=enrolled ``` ### Example Response ```json theme={null} { "learners": [ { "id": "uuid-1", "name": "John Doe", "email": "john@example.com", "profileImage": "https://example.com/john.jpg", "enrolledCourses": [1, 2, 5], "completedCourses": [1], "progress": { "1": 100, "2": 45, "5": 10 } } ] } ``` *** ## Get Learner Purchases (Admin/Instructor) Retrieves all purchases (payments) for a specific learner. Requires admin or instructor authentication. ### Path Parameters Learner UUID ### Response Array of purchase objects Purchase ID Course ID Course title Course thumbnail URL Payment amount Payment currency Payment gateway: "stripe", "flutterwave" Payment status: "pending", "completed", "failed", "refunded" Payment type: "one-time" or "recurring" Purchase timestamp ### Example Request ```bash theme={null} curl https://your-domain.com/api/learners/uuid-here/purchases ``` *** ## User Types The platform supports three user types: | User Type | Description | Permissions | | -------------- | ---------------------- | ----------------------------------------------- | | **user** | Regular learner | Enroll in courses, view content, submit quizzes | | **instructor** | Course creator | All learner permissions + create courses | | **admin** | Platform administrator | Full access to all features and data | *** ## Profile Auto-Creation When a new user signs up, the GET `/api/profile` endpoint automatically: 1. Checks if a profile exists 2. If not, creates one with: * `user_type`: "user" (default) * `name`: Derived from email or user metadata * `currency`: "USD" (default) 3. Returns the profile This ensures every authenticated user always has a valid profile. *** ## Error Codes | Status Code | Description | | ----------- | -------------------------------------- | | 200 | Success | | 400 | Bad Request - Invalid parameters | | 401 | Unauthorized - Authentication required | | 403 | Forbidden - Insufficient permissions | | 404 | Not Found - Profile or user not found | | 500 | Internal Server Error | # Branding and theming Source: https://docs.easelms.org/features/branding Customize your platform's visual identity with logos, colors, and SEO settings Make EaseLMS truly yours by customizing the platform's branding, appearance, and search engine optimization settings. ## Overview EaseLMS provides comprehensive branding customization to replace default EaseLMS branding with your own: * **Platform identity** - Name and description * **Visual branding** - Logos and favicon * **SEO optimization** - Meta tags and social sharing * **Email branding** - Consistent branding in notifications * **Theme support** - Light and dark mode compatibility All branding changes apply instantly across your entire platform, including the admin panel, learner interface, and email notifications. ## Accessing brand settings Configure branding from the admin dashboard: Go to **Admin → Settings** in the sidebar. Click the **Brand** tab to access branding options. Click the **Edit** button to enable editing of brand settings. After making changes, click **Save** to apply them platform-wide. Changes are saved to the database and take effect immediately. No server restart required. ## Platform identity ### Platform name Your platform's name appears throughout the application: * Browser tab titles * Email headers and subject lines * Admin dashboard header * Login pages * Footer copyright **Example:** ``` EaseLMS → Your Academy Name ``` ### Platform description A brief description of your platform used for: * SEO meta descriptions (if no custom SEO description is set) * Social media sharing * Email templates * About sections **Character limit:** None, but keep it concise (recommended 150-200 characters) **Example:** ``` Your Academy Name is a modern learning platform offering professional development courses in technology, business, and creative skills. ``` ## Visual branding ### Logo configuration EaseLMS uses two logo versions to ensure visibility in both light and dark modes: Displayed in **light mode** and on **light backgrounds**. Use a dark-colored logo that contrasts well with white/light backgrounds. Displayed in **dark mode** and on **dark backgrounds**. Use a light-colored logo that contrasts well with black/dark backgrounds. #### Logo requirements **Format:** * SVG (recommended for scalability) * PNG with transparent background * JPEG (not recommended - no transparency) **Dimensions:** * Width: 150-300px recommended * Height: 40-60px recommended * Aspect ratio: Maintain consistent proportions **File size:** * Maximum: 5MB * Recommended: Under 500KB for fast loading **Best practices:** * Use vector format (SVG) for crisp display at all sizes * Ensure transparent background for PNG files * Test logo visibility on both light and dark backgrounds * Keep logos simple and readable at small sizes SVG logos may not display in PDF certificates. For certificate compatibility, also provide PNG versions. ### Uploading logos In Brand Settings, click the upload area for the logo you want to change (dark or light). Choose your logo file from your computer. The file will upload automatically. The preview shows how your logo looks on the appropriate background color. Click **Save** to apply the new logo platform-wide. ### Favicon The favicon is the small icon that appears in: * Browser tabs * Bookmark lists * Browser history * Mobile home screen shortcuts **Requirements:** * **Format**: PNG, ICO, or SVG * **Size**: 512x512px (recommended) or 256x256px minimum * **Shape**: Square * **File size**: Under 100KB **Best practices:** * Use simple, recognizable design * Ensure visibility at 16x16px (browser tab size) * Avoid fine details that won't be visible when scaled down * Use high contrast colors Upload a 512x512px favicon and browsers will automatically scale it down to appropriate sizes. ## Contact and URL settings ### Contact email The primary contact email for your platform: **Used in:** * Email footers and support links * Contact forms * Reply-to address for notifications * Support documentation **Example:** ``` support@youracademy.com ``` ### App URL The base URL where your platform is hosted: **Used in:** * Email templates (for generating links) * Course sharing links * Certificate verification URLs * API responses **Format:** ``` https://yourdomain.com ``` Include the protocol (`https://`) but no trailing slash. Update this when moving between development and production. ## SEO settings Optimize your platform for search engines and social media sharing. ### SEO title Custom title for search engine results: * **Default**: Uses platform name if not set * **Length**: 50-60 characters recommended * **Format**: Include key terms and brand name **Example:** ``` Online Learning Platform | Your Academy Name ``` ### SEO description Description shown in search results and social media previews: * **Default**: Uses platform description if not set * **Length**: 150-160 characters recommended * **Content**: Clearly describe what your platform offers **Example:** ``` Learn new skills with expert-led online courses in technology, business, and creative fields. Start learning today with Your Academy Name. ``` ### SEO keywords Comma-separated keywords for search engines: * **Format**: `keyword1, keyword2, keyword3` * **Quantity**: 5-10 keywords recommended * **Relevance**: Focus on terms your audience searches for **Example:** ``` online learning, professional development, tech courses, business training, creative skills ``` Modern search engines rely less on meta keywords, but they can still provide context. Focus more on quality content and descriptions. ### SEO image (Open Graph) Image displayed when sharing your platform on social media: **Requirements:** * **Format**: PNG or JPEG * **Dimensions**: 1200x630px (Open Graph standard) * **File size**: Under 5MB (under 1MB recommended) * **Content**: Branded image with text overlay explaining your platform **Used on:** * Facebook shares * Twitter cards * LinkedIn posts * WhatsApp previews * Slack link unfurls **Best practices:** * Include your logo * Add descriptive text * Use high-quality images * Ensure text is readable at thumbnail size * Test previews using social media debugging tools Use tools like Canva or Figma to create professional Open Graph images with templates specifically for 1200x630px. ## Theme support EaseLMS includes built-in light and dark mode support. ### How theming works * **System preference** - Detects user's OS theme preference * **Manual toggle** - Users can override with their preference * **Persistent** - Theme choice saved in user preferences * **Responsive** - Logos and colors adapt automatically ### Logo adaptation The platform automatically switches logos based on theme: | Theme | Logo Used | Background | | ---------- | ---------------- | ----------------- | | Light mode | Dark/Black logo | Light backgrounds | | Dark mode | Light/White logo | Dark backgrounds | ### Email theme support Email templates support both light and dark modes: * Recipient's email client determines theme * Appropriate logo version displayed * Colors optimized for readability * Consistent branding in all modes Not all email clients support dark mode. Emails gracefully fall back to light mode when needed. ## Branding in emails All email notifications automatically include your branding: ### Email header * Platform logo (theme-appropriate version) * Platform name in subject line * Branded header background ### Email content * Platform name in greetings and messaging * Consistent brand voice and tone * Branded buttons and CTAs ### Email footer * Contact email for support * App URL for navigation * Platform name in copyright notice * Links back to platform Update brand settings to immediately affect all future emails. Test by triggering an enrollment or completion. ## Branding checklist Use this checklist to ensure complete branding customization: ### Essential branding * [ ] Platform name configured * [ ] Platform description written * [ ] Dark logo uploaded and previewed * [ ] Light logo uploaded and previewed * [ ] Favicon uploaded (512x512px) * [ ] Contact email set * [ ] App URL configured ### SEO optimization * [ ] SEO title defined (50-60 characters) * [ ] SEO description written (150-160 characters) * [ ] SEO keywords added (5-10 relevant terms) * [ ] Open Graph image uploaded (1200x630px) ### Testing * [ ] Verify logos in light mode * [ ] Verify logos in dark mode * [ ] Check favicon in browser tab * [ ] Test email branding (send test enrollment) * [ ] Preview social media sharing with OG image * [ ] Confirm contact email is monitored * [ ] Verify all links work correctly ## Best practices ### Professional branding * **Consistency** - Use the same logos and colors across all materials * **Quality** - Invest in professional logo design * **Simplicity** - Keep branding clean and recognizable * **Scalability** - Ensure logos look good at all sizes ### SEO optimization * **Keywords** - Research what your audience searches for * **Descriptions** - Write clear, compelling copy * **Images** - Use high-quality, relevant images * **Updates** - Refresh SEO content periodically ### User experience * **Visibility** - Ensure logos are readable on all backgrounds * **Accessibility** - Maintain sufficient color contrast * **Performance** - Optimize image file sizes for fast loading * **Mobile** - Test branding on mobile devices Large image files can slow down your platform. Compress images without sacrificing quality using tools like TinyPNG or ImageOptim. # Certificates Source: https://docs.easelms.org/features/certificates Generate and customize professional certificates for course completion Reward learner achievements with beautiful, customizable certificates that are automatically generated upon course completion. ## Overview EaseLMS includes a powerful certificate generation system that creates professional PDF certificates with: * **Automatic generation** - Certificates created when learners complete courses * **Custom templates** - Upload your own certificate backgrounds * **Digital signatures** - Add instructor or organization signatures * **Unique certificate numbers** - Each certificate has a unique ID for verification * **PDF download** - Learners can download and share certificates * **Email delivery** - Automatic notifications when certificates are ready ## Enabling certificates Configure certificates when creating or editing a course: Go to **Admin → Courses** and select a course, or create a new course. In the course builder, find the **Certificate Settings** section. Toggle "Enable Certificate" to activate certificate generation for this course. Customize your certificate appearance, text, and signature (detailed below). Certificates are course-specific. Enable and configure them individually for each course that should award certificates. ## Certificate types Choose the certificate type that best matches your course: "Certificate of Completion" Text: "has successfully completed" Best for courses focused on finishing content. "Certificate of Participation" Text: "has successfully participated in" Best for workshops or events. "Certificate of Achievement" Text: "has successfully achieved" Best for courses with assessments. ### Custom certificate title Create your own certificate title: 1. Select **"Custom Title (Optional)"** from certificate type options 2. Enter your custom title (e.g., "Certificate of Excellence", "Professional Development Certificate") 3. The custom title replaces the default type on the certificate Custom titles give you complete flexibility to match your organization's terminology and branding. ## Certificate customization ### Certificate description Write a custom description that appears on the certificate: * **Character limit**: 150 characters maximum * **Placeholders**: Use `[Student Name]` and `[Course Name]` * **Formatting**: Placeholders render in **bold** for emphasis **Example descriptions:** ``` This certifies that [Student Name] has completed the [Course Name]. ``` ``` [Student Name] has demonstrated excellence in completing [Course Name]. ``` ``` Awarded to [Student Name] for successful participation in [Course Name]. ``` Placeholders count toward the 150-character limit. Keep descriptions concise and impactful. ### Certificate template Upload a custom background image for your certificates: **Requirements:** * **Format**: PNG, JPEG, or PDF * **Orientation**: Landscape (recommended) * **Size**: Maximum 5MB * **Dimensions**: A4 landscape ratio (842px × 595px or 1684px × 1190px) **Best practices:** * Use high-resolution images (300 DPI for print quality) * Ensure text areas are clear and uncluttered * Light backgrounds work best for readability * Include your organization's branding elements Create a landscape certificate background in your design tool (Canva, Photoshop, etc.). Click the upload area in Certificate Settings and select your file. The template preview appears immediately. Check that it looks correct. If no template is uploaded, EaseLMS uses a clean default certificate design with your platform branding. ### Digital signature Add an authentic signature to certificates: **Signature image:** * **Format**: PNG or JPEG * **Size**: Maximum 1MB * **Recommendation**: Use transparent PNG for best results * **Placement**: Bottom of certificate **Signature details:** * **Name of signer** - The person's name (e.g., "Jane Smith") * **Title of signer** - Their position (e.g., "Course Instructor", "Director of Education") These details appear below the signature on the certificate. ### Additional text Add optional supplementary text to certificates: * **Character limit**: 150 characters maximum * **Placement**: Below the main description * **Use cases**: Accreditation info, continuing education credits, terms **Example:** ``` Accredited by the Professional Development Board. Valid for 2 years. ``` ## Certificate generation Certificates are generated automatically when learners meet completion criteria. ### Completion criteria A certificate is generated when: 1. **Course has certificates enabled** in settings 2. **Learner completes 100% of lessons** in the course 3. **All lessons marked complete** including videos, documents, and quizzes ### Generation process When the last lesson is marked complete, the system detects 100% progress. EaseLMS automatically generates a PDF certificate with: * Learner's full name * Course title * Completion date * Unique certificate number * Configured customizations Learner receives an email with: * Congratulations message * Certificate download link * Certificate number for reference Certificate appears in the learner's dashboard under completed courses. ### Unique certificate numbers Every certificate includes a unique identifier: * **Format**: `CERT-{timestamp}-{random}` * **Example**: `CERT-1678901234-A3F7K9` * **Purpose**: Verification and authenticity * **Location**: Bottom of certificate Certificate numbers can be used to verify authenticity if learners share certificates with employers or institutions. ## Certificate delivery ### Email notifications Learners receive a professional email when their certificate is ready: **Email includes:** * Congratulations message * Course name and completion date * Direct download link to PDF certificate * Certificate number * Link to dashboard ### Accessing certificates Learners can access certificates multiple ways: 1. **Email link** - Click download link in notification email 2. **Dashboard** - View all certificates in the learner dashboard 3. **Course page** - Download from completed course details ### PDF format Certificates are delivered as high-quality PDF files: * **Standard**: A4 landscape (842pt × 595pt) * **Quality**: Print-ready resolution * **File size**: Optimized for web and email * **Compatibility**: Opens in any PDF reader ## Certificate design EaseLMS renders certificates with a professional layout: ### Default certificate design When no custom template is uploaded: * Clean, professional appearance * Platform logo (if configured) * Certificate type/title prominently displayed * Learner name in large, bold text * Course name clearly visible * Description with formatted placeholders * Signature with name and title * Issue date and certificate number * Additional text (if provided) ### Custom template design When a template is uploaded: * Your background image fills the entire certificate * Text and elements overlay the template * Maintains readability with proper contrast * Logo, signature, and text positioned automatically ### Font and styling Certificates use the **Poppins** font family: * **Regular** - Body text and descriptions * **Bold** - Names, titles, and placeholders * **Sizing** - Responsive to content length The certificate renderer automatically adjusts text size to prevent overflow while maintaining readability. ## Managing certificates ### Viewing issued certificates Admins can view all issued certificates: 1. Navigate to **Admin → Certificates** 2. See list of all generated certificates 3. Filter by course, learner, or date 4. Download certificate PDFs 5. View certificate details and metadata ### Re-generating certificates If you update certificate settings: * Existing certificates are **not** automatically updated * New completions use the updated settings * Manually re-generate certificates if needed ### Certificate storage Certificates are stored securely: * **Database records** - Certificate metadata and references * **File storage** - PDF files stored in AWS S3 * **Access control** - Only certificate owners can download * **Backup** - Included in regular platform backups ## Best practices ### Template design * **Keep it simple** - Avoid cluttered backgrounds * **Maintain contrast** - Ensure text is readable over the background * **Use landscape** - Matches standard certificate format * **Brand consistently** - Use your organization's colors and logo * **Test printing** - Verify templates look good printed ### Certificate text * **Be concise** - Keep descriptions short and impactful * **Use placeholders** - `[Student Name]` and `[Course Name]` personalize certificates * **Proofread carefully** - Errors on certificates are permanent * **Stay professional** - Maintain formal, achievement-focused language ### Signatures * **Use real signatures** - Scan or digitally sign for authenticity * **Transparent backgrounds** - PNG files look best * **Appropriate size** - Not too large or small * **Include credentials** - Add titles and credentials to increase value ### Course completion * **Clear requirements** - Tell learners what they need to complete * **Achievable goals** - Make sure courses can be reasonably completed * **Value-driven** - Ensure certificates represent real achievement * **Promote certificates** - Mention certificates in course descriptions Highlight certificate availability in course descriptions to increase enrollment and completion rates. ## Troubleshooting ### Certificate not generating **Check:** * Certificates are enabled in course settings * Learner has completed 100% of lessons * All lessons are marked complete in the database * No errors in server logs ### Image not appearing **Check:** * Image URL is accessible (not blocked) * Image format is supported (PNG, JPEG) * File size is within limits * Image is not SVG (PDFKit doesn't support SVG) ### Text overflow **Solution:** * Shorten certificate description * Reduce additional text length * Use concise course titles * Check character limits (150 characters) The certificate renderer includes overflow protection, but very long text may be truncated. Stay within character limits for best results. # Course management Source: https://docs.easelms.org/features/courses Create, manage, and publish courses with rich content and flexible enrollment options EaseLMS provides a comprehensive course management system that allows you to create engaging online courses with video lessons, quizzes, resources, and flexible enrollment options. ## Creating a course Build your course with an intuitive multi-step creator that guides you through the entire process. Set up your course fundamentals: * Course title and description * Course thumbnail image * Category and tags for organization * Course preview video (optional) Add lessons and organize them into sections: * **Video lessons** - Upload video content with progress tracking * **Document lessons** - Share PDFs, presentations, and other files * **Quiz lessons** - Test learner knowledge with interactive quizzes * **Mixed content** - Combine multiple content types in one lesson Organize lessons into sections for better structure and navigation. Choose how learners access your course: * **Free** - Open enrollment with registration required * **Paid** - One-time payment for lifetime access Set pricing in your preferred currency (USD or NGN). Configure additional course features: * Course prerequisites (require completion of other courses) * Assign instructors to the course * Enable certificates upon completion * Set course visibility (published/draft) ## Course builder features ### Lesson types EaseLMS supports multiple lesson formats to create diverse learning experiences: Upload video content with automatic progress tracking. Videos save learner progress automatically as they watch. Share PDFs, Word documents, presentations, images, and other downloadable materials. Create quizzes with multiple question types, automatic grading, and instant feedback. Combine videos, text, and resources in a single lesson for rich learning experiences. ### Bulk operations Manage multiple lessons efficiently: * Reorder lessons with drag-and-drop * Bulk delete lessons * Duplicate lessons to save time * Move lessons between sections ### Course preview Preview your course exactly as learners will see it before publishing. Test the learning experience, check video playback, and review quiz functionality. ## Enrollment modes ### Free courses Make your course accessible to all learners with free enrollment: * Learners must register an account * Instant enrollment upon registration * No payment required * Great for building your audience ### Paid courses Monetize your content with one-time course purchases: * Set your price in USD or NGN * Secure payment processing via Stripe or Flutterwave * Automatic enrollment after successful payment * Lifetime access for purchased courses You can change enrollment mode and pricing at any time. Existing enrollments are not affected by pricing changes. ## Course prerequisites Create learning paths by requiring learners to complete specific courses before enrolling: 1. Enable prerequisites in course settings 2. Select required courses from your catalog 3. Learners must complete all prerequisites to enroll 4. Prerequisites display on the course page Use prerequisites to build structured learning paths and ensure learners have foundational knowledge. ## Publishing and visibility ### Draft mode Build and refine your course without making it visible to learners: * Course is hidden from the course catalog * Only admins and instructors can access * Perfect for course development and updates ### Published mode Make your course available to learners: * Visible in the course catalog * Learners can browse and enroll * Appears in search results and recommendations Publishing a course doesn't automatically enroll existing users. Learners must enroll to access the content. ## Managing enrollments Track and manage learner enrollments from the admin dashboard: * View total enrollment count * See enrollment history and dates * Monitor payment status for paid courses * Export enrollment data ### Enrollment notifications Automatic email notifications keep you and your learners informed: * **Learner emails** - Enrollment confirmation with course access link * **Admin emails** - Notifications for new enrollments * **Payment confirmations** - Receipts for paid enrollments ## Course analytics Monitor course performance with built-in analytics: Track total enrollments, active learners, and enrollment trends over time. See how many learners have completed the course and average progress rates. Monitor sales performance for paid courses with revenue reports. Identify popular lessons and areas where learners drop off. ## Instructor collaboration Assign multiple instructors to collaborate on course delivery: * Add instructors from your team * Instructors can edit course content * Shared access to analytics and enrollments * Instructor names display on the course page Instructors must be added to your team in Settings → Team before you can assign them to courses. ## Best practices ### Course structure * **Start with learning objectives** - Define what learners will achieve * **Break content into digestible chunks** - Keep lessons focused and concise * **Use sections for organization** - Group related lessons together * **Include knowledge checks** - Add quizzes to reinforce learning ### Content quality * **High-quality thumbnails** - Use professional images that represent the course * **Preview videos** - Give learners a taste of your teaching style * **Clear descriptions** - Explain who the course is for and what they'll learn * **Downloadable resources** - Provide additional materials for reference ### Engagement * **Mix content types** - Combine videos, documents, and quizzes * **Set clear expectations** - Tell learners how long the course takes * **Offer certificates** - Motivate completion with achievement recognition * **Update regularly** - Keep content fresh and relevant # Email notifications Source: https://docs.easelms.org/features/email-notifications SendGrid-powered email notification system for user and admin communications EaseLMS includes a comprehensive email notification system powered by SendGrid that keeps learners and admins informed about important events. ## Overview The email notification system automatically sends professional, branded emails for: * **User events** - Enrollments, completions, certificates, payments * **Admin notifications** - New enrollments, payments, course completions * **System emails** - Welcome messages, password resets, confirmations All emails are: * **Branded** - Use your platform name and logo * **Responsive** - Look great on all devices * **Professional** - Clean, modern design * **Customizable** - Adapt to your platform settings ## SendGrid setup Configure SendGrid to enable email notifications: Sign up for a free account at [sendgrid.com](https://sendgrid.com). The free tier includes 100 emails per day. In SendGrid dashboard: * Go to Settings → Sender Authentication * Verify your email address or domain * Complete sender verification (required to send emails) Create an API key: * Navigate to Settings → API Keys * Click "Create API Key" * Select "Full Access" or "Restricted Access" with Mail Send permissions * Copy your API key (shown only once!) Add SendGrid credentials to your `.env.local` file: ```bash theme={null} SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxx SENDGRID_FROM_EMAIL=noreply@yourdomain.com SENDGRID_FROM_NAME=Your Platform Name SENDGRID_REPLY_TO=support@yourdomain.com ``` Keep your SendGrid API key secure. Never commit it to version control or share it publicly. ## Email types ### User emails Emails sent directly to learners: Sent when a new user signs up. **Includes:** * Personalized greeting * Platform introduction * Getting started tips * Dashboard link Sent when a user enrolls in a course. **Includes:** * Course name and description * Enrollment date * Course access link * Getting started instructions Sent when a user completes a course. **Includes:** * Congratulations message * Course name * Completion date * Certificate link (if available) Sent when a certificate is generated. **Includes:** * Certificate number * Download link * Course details * Sharing instructions Sent after successful payment. **Includes:** * Transaction ID and amount * Course access information * Receipt/invoice * Support contact Sent when payment processing fails. **Includes:** * Failure reason * Retry instructions * Alternative payment options * Support contact ### Admin emails Notifications sent to platform administrators: Notifies admins of new course enrollments. Includes user name, email, course title, and enrollment date. Notifies admins of successful payments. Includes transaction details, amount, user info, and course. Notifies admins when users complete courses. Includes user name, course title, and completion date. Admin emails are sent to the contact email configured in **Settings → Brand**. ## Email content ### Branded templates All emails automatically include your platform branding: * **Platform name** - From Settings → Brand * **Platform logo** - Header logo (light/dark mode support) * **Contact email** - Footer contact information * **App URL** - Links back to your platform Emails adapt to light and dark mode based on recipient preferences. ### Personalization Emails include personalized content: * **Recipient name** - "Hi, John" greeting * **Course details** - Specific course information * **Transaction data** - Payment amounts and IDs * **Dates** - Formatted enrollment/completion dates ### Email structure Each email follows a consistent structure: 1. **Header** - Platform logo and branding 2. **Greeting** - Personalized salutation 3. **Main content** - Event-specific information 4. **Call-to-action** - Prominent button (e.g., "Access Course") 5. **Additional details** - Supporting information 6. **Footer** - Contact info, links, and legal text ## Configuration options ### Platform settings Control notification behavior in **Settings → Platform**: Toggle whether enrollment confirmation emails are sent to learners. Enable/disable course completion congratulations emails. Allow platform-wide announcement emails to all users. Set the default email preference for new user accounts. Users can change this in their profile. ### Brand settings Configure email branding in **Settings → Brand**: * **Platform name** - Appears in email subject lines and headers * **Logo (dark)** - Used in email headers for light mode * **Logo (light)** - Used in email headers for dark mode * **Contact email** - Displayed in email footers * **App URL** - Base URL for links in emails Update brand settings to immediately affect all future emails. Test emails by triggering actions (like enrollments) to verify branding. ## Email delivery ### Sending process An action occurs (enrollment, payment, completion, etc.). EaseLMS prepares the email with relevant data and branding. Email is sent to SendGrid API for delivery. SendGrid delivers the email and returns confirmation. ### Delivery time Emails are typically delivered: * **Instantly** - Most emails sent within seconds * **SendGrid queue** - May take 1-5 minutes during high volume * **Recipient server** - Final delivery depends on recipient's email provider ### Tracking SendGrid provides delivery tracking: * **Activity Feed** - View all sent emails in SendGrid dashboard * **Delivery status** - See delivered, bounced, or failed emails * **Open rates** - Track if recipients open emails (optional) * **Click tracking** - Monitor link clicks (optional) ## Testing emails ### Test mode Before going live, test email delivery: Use your personal email as the `SENDGRID_FROM_EMAIL` during development. * Sign up a test account * Enroll in a course * Complete a course * Make a test payment Check your inbox for emails and verify: * Emails arrive promptly * Branding appears correctly * Links work properly * Content is accurate ### Email preview Verify email content: 1. Check SendGrid Activity Feed to see sent emails 2. Preview email HTML in browser 3. Test on different email clients (Gmail, Outlook, Apple Mail) 4. Verify mobile responsiveness Send test emails to multiple email providers to ensure compatibility and good deliverability. ## Best practices ### Deliverability * **Verify sender domain** - Use SendGrid domain authentication for better deliverability * **Warm up your domain** - Start with small volumes and gradually increase * **Avoid spam triggers** - Don't use all caps, excessive exclamation marks, or spam keywords * **Provide unsubscribe** - Include clear unsubscribe options (already built-in) ### Content * **Clear subject lines** - Make the email purpose immediately clear * **Concise content** - Keep emails focused and scannable * **Strong CTAs** - Use clear, action-oriented buttons * **Mobile-first** - Ensure emails look good on mobile devices ### Branding * **Consistent visuals** - Use the same logo and colors across all emails * **Professional tone** - Maintain a consistent voice * **Contact information** - Always include support contact * **Legal compliance** - Include required legal text and privacy policy links ### Performance * **Monitor sending limits** - Track your SendGrid usage * **Handle failures gracefully** - Log failed sends and retry if needed * **Respect preferences** - Honor user email preferences * **Avoid over-emailing** - Don't overwhelm users with too many emails SendGrid's free tier allows 100 emails/day. For platforms with more users, upgrade to a paid plan with higher sending limits. ## Troubleshooting ### Emails not sending **Check:** * SendGrid API key is correctly configured * API key has Mail Send permissions * Sender email is verified in SendGrid * No SendGrid account restrictions or suspensions * Server logs for error messages ### Emails going to spam **Solutions:** * Complete SendGrid domain authentication * Avoid spam trigger words in subject/content * Ensure unsubscribe link is present * Build sender reputation gradually * Ask recipients to whitelist your sender address ### Branding not appearing **Check:** * Platform settings are saved correctly * Logo URLs are accessible (not blocked) * Image formats are supported (PNG, JPG) * Brand settings cache is cleared (restart server) ### Wrong content in emails **Verify:** * Correct data is passed to email templates * Environment variables are up to date * No caching issues with email templates * User and course data is accurate in database If emails consistently fail to send, check your SendGrid account status and daily sending limits. ## Advanced configuration ### Custom templates While EaseLMS includes default templates, you can customize them: 1. Modify email templates in `/lib/email/email-templates.ts` 2. Update email HTML and text versions 3. Maintain responsive design 4. Test thoroughly before deploying ### Webhook integration SendGrid webhooks provide real-time delivery updates: * Configure webhook endpoint in SendGrid dashboard * Receive delivery, bounce, and open notifications * Log email events for analytics * Handle bounces and unsubscribes automatically ### Localization Support multiple languages: * Detect user language preference * Load appropriate email template * Translate email content * Maintain separate templates per language Multi-language email support requires custom development. The default system sends emails in English. # Learner experience Source: https://docs.easelms.org/features/learners Discover how learners interact with courses, track progress, and achieve their learning goals EaseLMS provides an intuitive, engaging learning experience that keeps learners motivated and tracks their progress automatically. ## Learner dashboard When learners log in, they see a personalized dashboard with everything they need: Quick access to active courses with progress indicators and continue learning buttons. View all completed courses and earned certificates in one place. Personalized course recommendations based on interests and learning history. Track total courses enrolled, completion rate, and learning streaks. ### Dashboard features **Continue learning** The dashboard shows courses in progress with: * Visual progress bars showing completion percentage * "Continue" button that jumps to the next incomplete lesson * Course thumbnail and title for quick recognition **Welcome message** Personalized greeting with the learner's first name creates a welcoming experience. ## Course discovery Learners can explore your entire course catalog with powerful discovery features: ### Browsing courses The course catalog displays all published courses with: * Eye-catching thumbnails * Course title and description * Instructor names * Enrollment information (free or paid) * Preview videos for featured courses ### Search and filtering Help learners find the right course: * Search by course title or keywords * Filter by category or tags * Sort by newest, popular, or recommended * View course prerequisites before enrolling Course preview videos help learners understand your teaching style before enrolling. ## Course enrollment The enrollment process is seamless and adapts to your course settings: Learners browse the course catalog and click on a course to view details. The course page shows: * Complete course description * Course curriculum and lessons * Instructor information * Prerequisites (if any) * Enrollment requirements **For free courses:** * Click "Enroll Now" button * Instant access to course content * Confirmation email sent **For paid courses:** * Click "Purchase Course" button * Complete secure payment * Automatic enrollment after payment * Receipt and access email sent ### Enrollment notifications Learners receive email confirmations: * Welcome email upon enrollment * Course access link and instructions * Payment receipt (for paid courses) * Getting started tips ## Course player The course player provides an immersive learning environment optimized for all devices. ### Navigation **Lesson sidebar** * Complete course curriculum in a collapsible sidebar * Visual indicators for completed lessons (checkmarks) * Current lesson highlighted * Click any lesson to jump directly to it **Progress tracking** * Overall course progress bar * Individual lesson completion status * Automatic progress saving ### Content delivery Full-featured video player with: * Play/pause controls * Volume adjustment * Fullscreen mode * Progress saving (resume where you left off) * Playback speed control View documents directly in the browser or download for offline access. Take quizzes with: * Multiple question types * Instant feedback * Score display * Ability to retake Download course materials, worksheets, and supplementary files. ### Mobile-responsive design The course player adapts perfectly to all screen sizes: * Mobile-optimized navigation * Touch-friendly controls * Responsive video player * Readable text on all devices Learners can switch devices seamlessly. Progress is saved in real-time and syncs across all devices. ## Progress tracking Automatic progress tracking helps learners stay motivated and see their achievements. ### How progress is calculated * **Video lessons** - Marked complete when watched (progress saves automatically) * **Document lessons** - Marked complete when viewed or downloaded * **Quiz lessons** - Marked complete when submitted (regardless of score) * **Overall progress** - Percentage based on completed lessons vs. total lessons ### Progress indicators Learners see their progress everywhere: * Dashboard progress bars for each course * Course player shows overall completion percentage * Checkmarks on completed lessons in the sidebar * Visual feedback on lesson completion ### Completion tracking When a learner completes all lessons: * Course marked as 100% complete * Completion notification email sent * Certificate generated (if enabled) * Course moves to "Completed" section on dashboard Progress is saved automatically in real-time. Learners never lose their place, even if they close the browser. ## Quizzes and assessments Interactive quizzes help reinforce learning and test knowledge. ### Taking quizzes Click on a quiz lesson to begin. Instructions and question count display at the top. Answer multiple-choice, true/false, or short answer questions. Take your time - there's no timer unless configured. Submit your answers to see instant results with correct answers highlighted and explanations provided. Retake the quiz to improve your score or reinforce learning. ### Quiz features * **Instant feedback** - See correct answers immediately after submission * **Score tracking** - View your quiz scores and improvement over time * **Unlimited attempts** - Retake quizzes to master the material * **Progress credit** - Quiz completion counts toward course progress ## Certificates Earning certificates recognizes learner achievements and provides proof of completion. ### Earning certificates When a course has certificates enabled: 1. Complete all lessons in the course (100% progress) 2. Certificate automatically generated 3. Email notification sent with download link 4. Access certificates from the learner dashboard ### Certificate features * **Professional design** - Beautiful, branded certificates * **Unique certificate number** - Each certificate has a unique ID * **Personalization** - Includes learner name, course title, and completion date * **PDF download** - Download and share certificates * **Digital signatures** - Instructor signatures (if configured) Learners can share certificate PDFs on LinkedIn, include them in resumes, or print them for display. ## User profile Learners can manage their account and preferences: ### Profile settings * **Personal information** - Update name and profile picture * **Email preferences** - Control notification settings * **Password management** - Change password securely * **Account details** - View enrollment history and certificates ### Notification preferences Learners can control which emails they receive: * Enrollment confirmations * Course completion notifications * Certificate availability alerts * Platform announcements ## Purchase history For learners who purchase courses, the purchase page shows: * Complete transaction history * Payment receipts * Course access confirmation * Invoice downloads ### Payment information * Transaction ID and date * Amount paid and currency * Payment method used * Course purchased * Receipt download button All payment data is secure and processed through trusted payment gateways (Stripe or Flutterwave). ## Best practices for learner engagement ### Course completion strategies * **Set clear milestones** - Break courses into achievable sections * **Provide progress feedback** - Show learners how far they've come * **Offer certificates** - Give learners a reason to complete courses * **Send reminders** - Encourage learners to continue inactive courses ### Support and help * **Clear instructions** - Provide onboarding guidance for new learners * **Resource downloads** - Include helpful supplementary materials * **Contact information** - Make it easy for learners to get help * **FAQ section** - Answer common questions proactively # Payment integration Source: https://docs.easelms.org/features/payments Monetize your courses with secure payment processing via Stripe and Flutterwave EaseLMS integrates with leading payment gateways to help you monetize your courses with secure, reliable payment processing. ## Supported payment gateways Choose the payment gateway that works best for your audience and location: Global payment processing with support for credit cards, debit cards, and digital wallets in 135+ currencies. African-focused payment gateway supporting cards, mobile money, bank transfers, and USSD in 34+ African countries. ## Setting up payments ### Stripe setup Sign up at [stripe.com](https://stripe.com) and complete account verification. From your Stripe dashboard: * Navigate to Developers → API keys * Copy your Publishable key * Copy your Secret key (keep this secure!) Add these to your `.env.local` file: ```bash theme={null} STRIPE_SECRET_KEY=sk_test_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... ``` Create a test course with paid enrollment and use Stripe's test card numbers to verify the integration. ### Flutterwave setup Sign up at [flutterwave.com](https://flutterwave.com) and complete business verification. From your Flutterwave dashboard: * Navigate to Settings → API * Copy your Public key * Copy your Secret key Add these to your `.env.local` file: ```bash theme={null} FLUTTERWAVE_SECRET_KEY=FLWSECK_TEST-... NEXT_PUBLIC_FLUTTERWAVE_PUBLIC_KEY=FLWPUBK_TEST-... ``` Use Flutterwave's test mode to verify payments work correctly before going live. Never commit API keys to version control. Always use environment variables and keep secret keys secure. ## Currency support EaseLMS supports multiple currencies based on your payment gateway: ### Default currency Set your platform's default currency in **Settings → Platform**: * **USD** - US Dollar (recommended for Stripe, global audience) * **NGN** - Nigerian Naira (recommended for Flutterwave, African audience) ### Per-course currency While creating or editing a course, you can: * Use the platform default currency * Override with a specific currency for that course * Display prices in the selected currency The default currency setting helps streamline course creation, but you have full flexibility to set different currencies for individual courses. ## Payment flow Understand how payments work from the learner's perspective: Learner browses the course catalog and selects a course with paid enrollment. The course page displays: * Course price in the configured currency * "Purchase Course" button * Payment gateway options (Stripe/Flutterwave) Learner clicks "Purchase Course" and selects their preferred payment method: **Stripe checkout:** * Credit/debit card payment * Secure hosted checkout page * Support for 3D Secure authentication **Flutterwave checkout:** * Card payments * Mobile money (MTN, Airtel, etc.) * Bank transfer * USSD codes The payment gateway securely processes the transaction and returns the result to EaseLMS. On successful payment: * Learner automatically enrolled in the course * Payment confirmation email sent * Receipt generated * Immediate course access granted ### Payment failure handling If a payment fails: * Learner sees a clear error message * Payment failure email sent (if configured) * No enrollment created * Learner can retry payment * Admin notified of failed transaction ## Payment webhooks EaseLMS uses webhooks to receive real-time payment confirmations from payment gateways. ### How webhooks work 1. **Payment processed** - Gateway processes the payment 2. **Webhook sent** - Gateway sends confirmation to your server 3. **Verification** - EaseLMS verifies the webhook authenticity 4. **Enrollment created** - System creates enrollment and sends notifications 5. **User notified** - Learner receives confirmation email ### Webhook endpoints EaseLMS provides these webhook endpoints: * **Stripe webhook**: `/api/webhooks/stripe` * **Flutterwave webhook**: `/api/webhooks/flutterwave` Webhooks ensure reliable payment processing even if the user closes their browser before the payment completes. ## Transaction management Track and manage all payment transactions from the admin dashboard. ### Viewing purchases Access purchase history in **Admin → Purchases**: * Transaction ID and reference * Learner name and email * Course purchased * Amount and currency * Payment gateway used * Transaction date and time * Payment status (success/failed) ### Payment records Each payment record includes: Complete information about the payment including amount, currency, and gateway. Learner details including name, email, and enrollment date. Course title, ID, and enrollment status. Transaction ID, payment method, and gateway-specific data. ### Exporting data Export payment data for accounting and reporting: * Download transaction history as CSV * Filter by date range, course, or payment gateway * Include metadata for reconciliation ## Email notifications Automatic email notifications keep everyone informed about transactions: ### Learner emails **Payment confirmation** * Sent immediately after successful payment * Includes transaction ID and amount * Course access link * Receipt/invoice **Payment failed** * Sent when payment fails * Includes failure reason * Retry instructions ### Admin emails **New payment notification** * Sent when payment succeeds * Includes learner details * Transaction amount and ID * Link to admin dashboard Configure email settings in **Settings → Platform** to control which notifications are sent. ## Multi-currency handling When working with multiple currencies: ### Exchange rates Optionally configure exchange rate API for currency conversion: ```bash theme={null} EXCHANGERATE_API_KEY=your_api_key ``` This enables: * Real-time currency conversion * Display prices in multiple currencies * Accurate reporting across currencies ### Currency display Courses display prices in their configured currency: * **USD** - \$99.00 * **NGN** - ₦35,000.00 The currency symbol and formatting adjust automatically. ## Testing payments ### Test mode Both payment gateways offer test modes for safe testing: **Stripe test cards:** ``` Success: 4242 4242 4242 4242 3D Secure: 4000 0025 0000 3155 Declined: 4000 0000 0000 9995 ``` **Flutterwave test mode:** * Use test API keys (starting with `FLWSECK_TEST-`) * Test cards provided in dashboard * Simulate different payment methods ### Testing checklist Test that successful payments create enrollments and send confirmation emails. Test that failed payments don't create enrollments and show error messages. Verify webhooks are received and processed correctly. Confirm all payment emails are sent with correct information. ## Production checklist Before going live with payments: * [ ] Switch to production API keys (remove `_TEST` keys) * [ ] Enable webhook endpoints in gateway dashboards * [ ] Test production payment with real card * [ ] Verify SSL certificate is active on your domain * [ ] Configure payout settings in gateway dashboard * [ ] Set up business verification with payment gateway * [ ] Test refund process (if applicable) * [ ] Review and comply with payment gateway terms Always test payments thoroughly in test mode before switching to production keys. ## Security best practices ### API key management * **Never expose secret keys** - Keep them in server-side environment variables only * **Rotate keys regularly** - Update keys periodically for security * **Use separate keys** - Different keys for test/production environments * **Restrict key permissions** - Use minimum required permissions ### PCI compliance EaseLMS is designed to minimize PCI compliance requirements: * Card data never touches your servers * Payment forms hosted by gateways * Secure redirects for checkout * No card storage on your platform ### Webhook security Protect webhook endpoints: * Verify webhook signatures * Use HTTPS for webhook URLs * Validate payment amounts * Check transaction uniqueness EaseLMS automatically handles webhook signature verification to prevent fraudulent payment confirmations. # Customization Guide Source: https://docs.easelms.org/guides/customization Learn how to customize EaseLMS branding, features, and integrate payment gateways EaseLMS provides extensive customization options to make the platform your own. This guide covers branding, feature configuration, and payment integration. ## Platform Branding Customize your platform's appearance and identity through the admin dashboard. ### Access Branding Settings Sign in with an admin account to access platform settings. Go to **Settings → Brand** in the admin dashboard. ### Customizable Elements #### Platform Name and Description ``` Platform Name: Your LMS Name Platform Description: Your tagline or mission statement ``` These appear in: * Page titles and meta tags * Email templates * Footer text * Welcome messages #### Logos Upload two versions of your logo: * **Light Mode Logo** - Displayed on light backgrounds * **Dark Mode Logo** - Displayed on dark backgrounds Recommended size: 200x50px (PNG or SVG format). Transparent backgrounds work best. #### Favicon Upload a custom favicon that appears in browser tabs. Recommended size: 32x32px or 64x64px (ICO, PNG, or SVG format). #### Contact Information ``` Contact Email: support@yourdomain.com App URL: https://yourdomain.com ``` Used in: * Email templates * Footer links * Support references ### SEO Configuration Optimize your platform for search engines: ``` SEO Title: Your LMS - Learn Online SEO Description: Professional online learning platform with courses in... SEO Keywords: online learning, courses, education, certification SEO Image: URL to your social sharing image ``` The SEO image (Open Graph image) should be 1200x630px for optimal social media sharing. ## Theme Customization EaseLMS uses Tailwind CSS for styling, making it easy to customize colors and design. ### Color Scheme Edit `apps/lms/app/globals.css` to customize colors: ```css theme={null} @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; /* Main brand color */ --primary-foreground: 210 40% 98%; /* ... more color variables */ } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; /* ... dark mode colors */ } } ``` ### Custom Fonts EaseLMS uses the Geist font family. To change: 1. Import your font in `apps/lms/app/layout.tsx`: ```typescript theme={null} import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'] }); export default function RootLayout({ children }) { return ( {children} ); } ``` ## Payment Integration EaseLMS supports two payment gateways: Stripe (global) and Flutterwave (African markets). ### Stripe Integration #### Setup Stripe Sign up at [stripe.com](https://stripe.com) 1. Go to **Developers → API keys** 2. Copy your **Publishable key** and **Secret key** 3. For testing, use keys starting with `pk_test_` and `sk_test_` ```env theme={null} STRIPE_SECRET_KEY=sk_test_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... ``` 1. Go to **Developers → Webhooks** 2. Click **Add endpoint** 3. URL: `https://yourdomain.com/api/webhooks/stripe` 4. Events: Select `payment_intent.succeeded` and `payment_intent.payment_failed` 5. Save the webhook signing secret #### Test Stripe Integration Use Stripe's test card numbers: ``` Successful payment: 4242 4242 4242 4242 Requires authentication: 4000 0025 0000 3155 Declined: 4000 0000 0000 9995 Expiry: Any future date CVV: Any 3 digits ``` ### Flutterwave Integration #### Setup Flutterwave Sign up at [flutterwave.com](https://flutterwave.com) 1. Go to **Settings → API** 2. Copy your **Public key** and **Secret key** 3. Use test keys for development ```env theme={null} FLUTTERWAVE_SECRET_KEY=FLWSECK_TEST-... NEXT_PUBLIC_FLUTTERWAVE_PUBLIC_KEY=FLWPUBK_TEST-... ``` 1. Go to **Settings → Webhooks** 2. URL: `https://yourdomain.com/api/webhooks/flutterwave` 3. Save the secret hash ### Multi-Currency Support EaseLMS supports multiple currencies through: 1. **User-selected currency** - Stored in user profile 2. **Automatic conversion** - Using exchangerate-api.com 3. **Gateway-specific currencies** - Stripe and Flutterwave handle local currencies #### Configure Currency Exchange ```env theme={null} EXCHANGERATE_API_KEY=your_api_key ``` Get a free API key at [exchangerate-api.com](https://www.exchangerate-api.com). ### Enrollment Modes Courses can have different enrollment types: #### Free Enrollment ```typescript theme={null} enrollment_mode: 'free' ``` Users can enroll without payment. #### One-Time Purchase ```typescript theme={null} enrollment_mode: 'buy' price: 99.99 currency: 'USD' ``` Users pay once for lifetime access. #### Recurring Subscription ```typescript theme={null} enrollment_mode: 'recurring' recurring_price: 29.99 currency: 'USD' ``` Monthly subscription model (requires additional setup). #### Closed Enrollment By setting `enrollment_mode` to a custom value or managing enrollment manually through the admin panel. ## Email Customization ### Configure SendGrid 1. Sign up at [sendgrid.com](https://sendgrid.com) 2. Free tier: 100 emails/day 1. Go to **Settings → Sender Authentication** 2. Verify your domain or single sender email 3. Complete authentication process 1. Go to **Settings → API Keys** 2. Create new key with **Mail Send** permissions 3. Copy the key (shown only once) ```env theme={null} SENDGRID_API_KEY=SG.xxx... SENDGRID_FROM_EMAIL=noreply@yourdomain.com SENDGRID_FROM_NAME=Your LMS Name SENDGRID_REPLY_TO=support@yourdomain.com ``` ### Email Templates EaseLMS sends these automated emails: **User Emails:** * Welcome email on signup * Course enrollment confirmation * Course completion notification * Certificate ready notification * Payment confirmation * Payment failure alert **Admin Emails:** * New enrollment notification * New payment received * Course completion alert Email templates automatically use your platform branding (name, logo, colors) configured in Settings → Brand. ## Certificate Customization Customize certificates for each course. ### Certificate Settings ```typescript theme={null} certificate_enabled: true certificate_type: 'completion' | 'achievement' | 'participation' certificate_title: 'Certificate of Completion' certificate_description: 'Has successfully completed...' signature_name: 'John Doe' signature_title: 'CEO' signature_image: 'url_to_signature_image' ``` ### Certificate Templates Edit certificate templates in `apps/lms/lib/certificates/`: ```typescript:apps/lms/lib/certificates/template.ts theme={null} export function generateCertificate(data: CertificateData) { // Customize PDF generation // Uses PDFKit library } ``` ## Feature Flags Enable or disable features through platform settings. ### Available Settings In the `platform_settings` table: ```sql theme={null} -- Currency settings default_currency: 'USD' -- Notification toggles course_enrollment_notifications: true course_completion_notifications: true platform_announcements: true user_email_notifications: true ``` Update through the admin UI or directly in Supabase. ## Course Settings Customize learning experience per course: ### Progress Tracking ```typescript theme={null} requires_sequential_progress: true // Must complete lessons in order minimum_quiz_score: 80 // Minimum score to pass (0-100) ``` ### Quiz Configuration ```typescript theme={null} quiz_settings: { enabled: true, shuffle_quiz: true, max_attempts: 3, show_correct_answers: true, allow_multiple_attempts: true, time_limit: 3600, // seconds passing_score: 70 // percentage } ``` ### Lesson Types Supported lesson types: * **video** - Video content with progress tracking * **text** - Rich text content (Tiptap editor) * **mixed** - Combination of video and text ## Custom Components Extend EaseLMS with custom components: ### Add Custom Page Create a new page in `apps/lms/app/`: ```typescript:apps/lms/app/custom-page/page.tsx theme={null} export default function CustomPage() { return (

Custom Page

{/* Your content */}
); } ``` ### Add to Navigation Edit `apps/lms/components/navigation.tsx` to add menu items. ## Advanced Customization ### Modify Database Schema To add custom fields: 1. Add column in Supabase SQL Editor: ```sql theme={null} ALTER TABLE courses ADD COLUMN custom_field TEXT; ``` 2. Update TypeScript types in `apps/lms/types/` 3. Update forms and components to use new fields ### Custom Authentication EaseLMS uses Supabase Auth. To customize: * Modify sign-up flow in `apps/lms/app/auth/` * Add OAuth providers in Supabase dashboard * Customize email templates in Supabase ## Troubleshooting ### Branding changes not appearing 1. Clear browser cache 2. Check that changes were saved in database 3. Restart development server ### Payment gateway not working 1. Verify API keys are correct 2. Check webhook configuration 3. Review payment gateway dashboard for errors 4. Test with provided test cards ### Emails not sending 1. Verify SendGrid API key 2. Check sender email is verified 3. Review SendGrid activity log 4. Check spam folder ## Next Steps * Review [environment variables](/guides/environment-variables) for all configuration options * Set up [deployment](/guides/deployment) for production * Explore the codebase to understand component structure # Database Setup Source: https://docs.easelms.org/guides/database-setup Complete guide to setting up Supabase database and running migrations for EaseLMS EaseLMS uses Supabase (PostgreSQL) as its database. This guide walks you through setting up your database and running the necessary migrations. ## Prerequisites Before you begin, make sure you have: * A Supabase account (free tier works perfectly) * Basic understanding of SQL * Your EaseLMS repository cloned locally ## Create a Supabase Project Go to [supabase.com](https://supabase.com) and sign up or log in to your account. * Click "New Project" * Choose your organization * Enter a project name (e.g., "easelms-production") * Set a strong database password (save this securely) * Select a region close to your users * Click "Create new project" Wait approximately 2 minutes for Supabase to provision your project. ## Get Your Supabase Credentials In your Supabase project dashboard, go to **Settings → API** You'll need three values: * **Project URL** - This is your `NEXT_PUBLIC_SUPABASE_URL` * **anon public key** - This is your `NEXT_PUBLIC_SUPABASE_ANON_KEY` * **service\_role secret key** - This is your `SUPABASE_SERVICE_ROLE_KEY` Keep your `service_role` key secure! It has full access to your database and should never be exposed in client-side code. Add these values to your `.env.local` file: ```env theme={null} NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key ``` ## Run Database Migration The database migration file creates all necessary tables, indexes, and security policies for EaseLMS. In your Supabase project, navigate to **SQL Editor** from the left sidebar. Click **New Query** to open a blank SQL editor. Open the file `apps/lms/supabase/migrations/database_setup.sql` from your EaseLMS repository and copy the entire contents. * Paste the SQL into the Supabase SQL Editor * Click **Run** (or press Cmd/Ctrl + Enter) * Wait for execution to complete You should see "Success. No rows returned" when the migration completes successfully. ## Verify Database Setup After running the migration, verify that all tables were created correctly. Go to **Table Editor** in your Supabase dashboard. You should see the following tables: * `profiles` - User profile information * `courses` - Course data * `lessons` - Individual lesson content * `enrollments` - Course enrollment records * `progress` - Student progress tracking * `payments` - Payment transactions * `certificates` - Generated certificates * `instructors` - Instructor information * `resources` - Downloadable resources * `quiz_questions` - Quiz questions * `quiz_settings` - Quiz configuration * `quiz_attempts` - Quiz attempt tracking * `quiz_results` - Quiz results * `course_instructors` - Course-instructor relationships * `course_prerequisites` - Course prerequisites * `lesson_resources` - Lesson-resource relationships * `platform_settings` - Platform-wide settings ## Database Schema Overview ### Core Tables #### profiles Stores user profile information, linked to Supabase Auth users. ```sql theme={null} CREATE TABLE profiles ( id UUID PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, user_type TEXT NOT NULL DEFAULT 'learner', profile_image TEXT, currency TEXT NOT NULL DEFAULT 'USD', bio TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); ``` #### courses Stores course information with pricing, enrollment modes, and certificate settings. ```sql theme={null} CREATE TABLE courses ( id SERIAL PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL, image TEXT, price NUMERIC(10, 2), currency TEXT DEFAULT 'USD', enrollment_mode TEXT DEFAULT 'free', certificate_enabled BOOLEAN DEFAULT FALSE, is_published BOOLEAN DEFAULT FALSE, created_by UUID REFERENCES profiles(id) ); ``` #### lessons Stores individual lesson content (video, text, or mixed). ```sql theme={null} CREATE TABLE lessons ( id SERIAL PRIMARY KEY, course_id INTEGER NOT NULL REFERENCES courses(id), title TEXT NOT NULL, type TEXT NOT NULL, video_url TEXT, text_content TEXT, order_index INTEGER NOT NULL, estimated_duration INTEGER DEFAULT 0 ); ``` ### Security Features The migration includes: * **Row Level Security (RLS)** - Enabled on all tables * **Security Policies** - Restrict data access based on user roles * **Automatic Triggers** - Update timestamps and create profiles automatically * **Indexes** - Optimize query performance The database uses PostgreSQL extensions `uuid-ossp` for UUID generation and `pgcrypto` for password hashing. ## User Roles and Permissions ### User Types * **user** - Regular learner with access to enrolled courses * **admin** - Full access to all platform features and data ### Automatic Profile Creation When a user signs up through Supabase Auth, a profile is automatically created via the `handle_new_user()` trigger function: ```sql theme={null} CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); ``` ## Create Your First Admin User After setting up the database, you'll need to create an admin user. Go to your EaseLMS application at `http://localhost:3000` and sign up for a new account. * Go to **Table Editor** in Supabase * Open the `profiles` table * Find your user record * Change `user_type` from `learner` to `admin` * Save the changes Log out of the application and log back in to see admin features. ## Troubleshooting ### Migration Fails with "relation already exists" This means you've already run the migration. If you need to reset: 1. Go to SQL Editor in Supabase 2. Run: `DROP SCHEMA public CASCADE; CREATE SCHEMA public;` 3. Re-run the migration script This will delete all data! Only do this in development. ### "permission denied for table" errors Check that Row Level Security policies are correctly applied. The migration script includes all necessary policies. ### Cannot insert into profiles table Ensure the `on_auth_user_created` trigger is active. Check in SQL Editor: ```sql theme={null} SELECT * FROM information_schema.triggers WHERE trigger_name = 'on_auth_user_created'; ``` ## Next Steps After setting up your database: 1. Configure your [environment variables](/guides/environment-variables) 2. Set up [AWS S3 for file storage](/guides/deployment#aws-s3-setup) 3. Configure [payment gateways](/guides/customization#payment-integration) 4. Start the development server with `npm run dev` # Deployment Guide Source: https://docs.easelms.org/guides/deployment Deploy EaseLMS to production on Vercel, Railway, or your own infrastructure This guide covers deploying EaseLMS to production. We'll walk through deploying on popular platforms and setting up the required services. ## Pre-Deployment Checklist Before deploying, ensure you have: * [ ] Supabase project created and database migrated * [ ] All environment variables documented * [ ] AWS S3 bucket configured (for production) * [ ] Payment gateways configured (if using paid courses) * [ ] SendGrid account set up (for email notifications) * [ ] Domain name ready (optional but recommended) ## Deployment Options EaseLMS can be deployed to various platforms: * **Vercel** - Recommended for beginners, zero-config Next.js hosting * **Railway** - Easy deployment with PostgreSQL support * **Docker** - Self-hosted with full control * **Traditional VPS** - Ubuntu/Debian server with PM2 ## Deploy to Vercel Vercel provides the easiest deployment experience for Next.js applications. ```bash theme={null} npm install -g vercel ``` 1. Push your code to GitHub, GitLab, or Bitbucket 2. Go to [vercel.com](https://vercel.com) and sign up 3. Click **Add New Project** 4. Import your repository * **Framework Preset**: Next.js * **Root Directory**: `apps/lms` * **Build Command**: `npm run build` * **Output Directory**: `.next` In the Vercel dashboard, add all your environment variables: ```env theme={null} NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... SUPABASE_SERVICE_ROLE_KEY=eyJ... NEXT_PUBLIC_APP_URL=https://yourdomain.com AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=wJa... AWS_S3_BUCKET_NAME=your-bucket STRIPE_SECRET_KEY=sk_live_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_... SENDGRID_API_KEY=SG... SENDGRID_FROM_EMAIL=noreply@yourdomain.com ``` Use production keys, not test keys! Click **Deploy** and wait for the build to complete (usually 2-3 minutes). 1. Go to **Settings → Domains** 2. Add your custom domain 3. Update DNS records as instructed 4. Update `NEXT_PUBLIC_APP_URL` to your domain ### Vercel CLI Deployment Alternatively, deploy from the command line: ```bash theme={null} cd apps/lms vercel ``` Follow the prompts to deploy. ## Deploy to Railway Railway offers a great deployment experience with built-in database support. Sign up at [railway.app](https://railway.app) 1. Click **New Project** 2. Select **Deploy from GitHub repo** 3. Authorize Railway and select your repository Railway should auto-detect Next.js. If not, set: * **Build Command**: `cd apps/lms && npm install && npm run build` * **Start Command**: `cd apps/lms && npm start` In the Railway dashboard: 1. Go to **Variables** tab 2. Add all your environment variables 3. Click **Deploy** Railway provides a URL like `https://your-app.up.railway.app`. Update `NEXT_PUBLIC_APP_URL` to this URL. ## Deploy with Docker For self-hosting with full control over your infrastructure. ### Create Dockerfile Create `Dockerfile` in `apps/lms/`: ```dockerfile theme={null} FROM node:18-alpine AS base # Install dependencies FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci # Build the application FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build # Production image FROM base AS runner WORKDIR /app ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT 3000 CMD ["node", "server.js"] ``` ### Create docker-compose.yml ```yaml theme={null} version: '3.8' services: app: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=production - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} - AWS_REGION=${AWS_REGION} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME} - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY} - SENDGRID_API_KEY=${SENDGRID_API_KEY} - SENDGRID_FROM_EMAIL=${SENDGRID_FROM_EMAIL} restart: unless-stopped ``` ### Deploy with Docker Compose ```bash theme={null} # Build and start docker-compose up -d # View logs docker-compose logs -f # Stop docker-compose down ``` ## AWS S3 Setup AWS S3 is required for production to handle file uploads and video streaming. 1. Sign in to [AWS Console](https://console.aws.amazon.com) 2. Navigate to **S3** 3. Click **Create bucket** 4. Choose a unique bucket name (e.g., `easelms-production`) 5. Select a region close to your users 6. **Uncheck** "Block all public access" (we'll use signed URLs) 7. Click **Create bucket** In your bucket settings, go to **Permissions → CORS** and add: ```json theme={null} [ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "DELETE"], "AllowedOrigins": ["https://yourdomain.com"], "ExposeHeaders": ["ETag"] } ] ``` 1. Go to **IAM → Users** 2. Click **Add users** 3. User name: `easelms-s3-user` 4. Access type: **Programmatic access** 5. Attach policy: **AmazonS3FullAccess** (or create custom policy) 6. Save the Access Key ID and Secret Access Key ```env theme={null} AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=wJa... AWS_S3_BUCKET_NAME=easelms-production ``` ### Optional: CloudFront CDN For better performance, set up CloudFront: 1. Go to **CloudFront** in AWS Console 2. Click **Create Distribution** 3. Origin domain: Select your S3 bucket 4. Origin access: **Legacy access identities** 5. Create new OAI and grant bucket permissions 6. Click **Create distribution** ```env theme={null} AWS_CLOUDFRONT_DOMAIN=d123456.cloudfront.net ``` ## Database Migration in Production Run the database migration on your production Supabase instance: Log in to your production Supabase project 1. Go to **SQL Editor** 2. Paste contents of `apps/lms/supabase/migrations/database_setup.sql` 3. Click **Run** 1. Sign up through your production app 2. In Supabase, go to **Table Editor → profiles** 3. Find your user and change `user_type` to `admin` ## Post-Deployment Configuration ### Configure Webhook Endpoints For payment gateways, set up webhooks: #### Stripe Webhooks 1. Go to [Stripe Dashboard → Webhooks](https://dashboard.stripe.com/webhooks) 2. Click **Add endpoint** 3. Endpoint URL: `https://yourdomain.com/api/webhooks/stripe` 4. Select events: `payment_intent.succeeded`, `payment_intent.payment_failed` 5. Save the webhook signing secret to your environment variables #### Flutterwave Webhooks 1. Go to Flutterwave Dashboard → Settings → Webhooks 2. Webhook URL: `https://yourdomain.com/api/webhooks/flutterwave` 3. Save the secret hash ### Verify Email Sending Test that SendGrid emails are working: 1. Create a test account on your production site 2. Verify you receive the welcome email 3. Check spam folder if not received 4. Verify sender domain in SendGrid if issues persist ## SSL/HTTPS Configuration Most platforms (Vercel, Railway) provide automatic SSL. For self-hosted: ### Using Let's Encrypt with Nginx ```bash theme={null} # Install certbot sudo apt install certbot python3-certbot-nginx # Get certificate sudo certbot --nginx -d yourdomain.com # Auto-renewal is configured automatically ``` ## Monitoring and Logging ### Vercel Analytics EaseLMS includes `@vercel/analytics`. It's automatically enabled on Vercel deployments. ### Custom Monitoring Consider adding: * **Sentry** - Error tracking * **LogRocket** - Session replay * **Plausible** - Privacy-friendly analytics ## Performance Optimization ### Enable Caching Configure appropriate cache headers in `next.config.js`: ```javascript theme={null} module.exports = { async headers() { return [ { source: '/images/:path*', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], }, ]; }, }; ``` ### Use Image Optimization Next.js automatically optimizes images. Ensure you're using the `` component. ## Backup Strategy ### Database Backups Supabase provides automatic daily backups on paid plans. For custom backups: ```bash theme={null} # Using pg_dump pg_dump -h db.xxx.supabase.co -U postgres -d postgres > backup.sql ``` ### File Storage Backups Enable S3 versioning: 1. Go to your S3 bucket 2. **Properties → Versioning** 3. Enable versioning ## Troubleshooting ### Build fails * Check Node.js version (minimum 18.0) * Verify all dependencies are in `package.json` * Check build logs for specific errors ### Environment variables not working * Ensure `NEXT_PUBLIC_` prefix for client variables * Redeploy after changing environment variables * Check variable names match exactly ### Database connection issues * Verify Supabase credentials * Check that database migration was run * Ensure RLS policies are enabled ### File upload not working * Verify AWS credentials * Check S3 bucket permissions * Ensure CORS is configured ## Next Steps After deploying: 1. [Customize your platform](/guides/customization) 2. Set up your first course 3. Configure platform branding 4. Test payment flows 5. Monitor performance and errors # Environment Variables Source: https://docs.easelms.org/guides/environment-variables Complete reference for all environment variables used in EaseLMS EaseLMS uses environment variables to configure various services and integrations. This guide provides a complete reference for all available variables. ## Setup Instructions Create a `.env.local` file in the `apps/lms/` directory of your project: ```bash theme={null} cd apps/lms touch .env.local ``` Never commit `.env.local` to version control. It's already included in `.gitignore`. ## Required Variables These variables are essential for EaseLMS to function: ### Supabase Configuration ```env theme={null} # Supabase Project URL NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url # Supabase Anonymous Key (safe to expose in client) NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key # Supabase Service Role Key (keep secret!) SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key ``` 1. Go to your Supabase project dashboard 2. Navigate to **Settings → API** 3. Copy the Project URL and API keys Paste the values into your `.env.local` file The `SUPABASE_SERVICE_ROLE_KEY` has full database access. Never expose it in client-side code or commit it to version control. ### Application URL ```env theme={null} # The URL where your application is accessible NEXT_PUBLIC_APP_URL=http://localhost:3000 ``` For production, change this to your actual domain: ```env theme={null} NEXT_PUBLIC_APP_URL=https://yourdomain.com ``` ## Optional Variables These variables enable additional features: ### AWS S3 Storage Required for uploading course videos, images, and resources. ```env theme={null} # AWS Region AWS_REGION=us-east-1 # AWS Access Credentials AWS_ACCESS_KEY_ID=your_aws_access_key AWS_SECRET_ACCESS_KEY=your_aws_secret_key # S3 Bucket Name AWS_S3_BUCKET_NAME=your_bucket_name # CloudFront Domain (optional, for CDN) AWS_CLOUDFRONT_DOMAIN=your_cloudfront_domain ``` While optional for development, AWS S3 is highly recommended for production to handle file uploads and video streaming. ### Payment Gateways Enable course payments through Stripe and Flutterwave. #### Stripe ```env theme={null} # Stripe Secret Key (server-side) STRIPE_SECRET_KEY=sk_test_... # Stripe Publishable Key (client-side) NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... ``` 1. Sign up at [stripe.com](https://stripe.com) 2. Go to **Developers → API keys** 3. Copy both the secret and publishable keys Use keys starting with `sk_test_` and `pk_test_` for development. Switch to live keys (`sk_live_` and `pk_live_`) for production. #### Flutterwave Popular for African markets with multi-currency support. ```env theme={null} # Flutterwave Secret Key (server-side) FLUTTERWAVE_SECRET_KEY=your_flutterwave_secret_key # Flutterwave Public Key (client-side) NEXT_PUBLIC_FLUTTERWAVE_PUBLIC_KEY=your_flutterwave_public_key ``` ### Currency Exchange Rates Enable automatic currency conversion using exchangerate-api.com. ```env theme={null} EXCHANGERATE_API_KEY=your_exchangerate_api_key ``` Get a free API key at [exchangerate-api.com](https://www.exchangerate-api.com). The free tier provides 1,500 requests per month. ### Email Notifications (SendGrid) Enable automated email notifications for users and admins. ```env theme={null} # SendGrid API Key SENDGRID_API_KEY=your_sendgrid_api_key # From Email Address SENDGRID_FROM_EMAIL=noreply@yourdomain.com # From Name SENDGRID_FROM_NAME=EaseLMS # Reply-To Address SENDGRID_REPLY_TO=support@yourdomain.com ``` Sign up at [sendgrid.com](https://sendgrid.com) - free tier includes 100 emails/day 1. Go to **Settings → API Keys** 2. Click **Create API Key** 3. Give it full access to **Mail Send** 4. Copy the key (shown only once) 1. Go to **Settings → Sender Authentication** 2. Verify your domain or single sender email 3. Use the verified email as `SENDGRID_FROM_EMAIL` #### Email Types Sent When configured, EaseLMS automatically sends: **User Emails:** * Welcome email on signup * Course enrollment confirmation * Course completion notification * Certificate ready notification * Payment confirmation * Payment failed alert **Admin Emails:** * New enrollment notification * New payment notification * Course completion notification ## Environment Variable Reference ### Complete Example Here's a complete `.env.local` file with all variables: ```env theme={null} # ============================================ # REQUIRED - Supabase # ============================================ NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc... SUPABASE_SERVICE_ROLE_KEY=eyJhbGc... # ============================================ # REQUIRED - Application # ============================================ NEXT_PUBLIC_APP_URL=http://localhost:3000 # ============================================ # OPTIONAL - AWS S3 Storage # ============================================ AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=wJalr... AWS_S3_BUCKET_NAME=easelms-uploads AWS_CLOUDFRONT_DOMAIN=d123456.cloudfront.net # ============================================ # OPTIONAL - Payment Gateways # ============================================ # Stripe STRIPE_SECRET_KEY=sk_test_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... # Flutterwave FLUTTERWAVE_SECRET_KEY=FLWSECK_TEST-... NEXT_PUBLIC_FLUTTERWAVE_PUBLIC_KEY=FLWPUBK_TEST-... # ============================================ # OPTIONAL - Currency Exchange # ============================================ EXCHANGERATE_API_KEY=your_api_key_here # ============================================ # OPTIONAL - Email (SendGrid) # ============================================ SENDGRID_API_KEY=SG.xxx... SENDGRID_FROM_EMAIL=noreply@yourdomain.com SENDGRID_FROM_NAME=EaseLMS SENDGRID_REPLY_TO=support@yourdomain.com ``` ## Variable Naming Conventions ### Public vs Private Variables * **`NEXT_PUBLIC_*`** - Safe to expose to the browser, accessible in client-side code * **No prefix** - Server-side only, never sent to the client Never prefix sensitive keys (like `SUPABASE_SERVICE_ROLE_KEY` or `STRIPE_SECRET_KEY`) with `NEXT_PUBLIC_` as this would expose them in the browser. ## Development vs Production ### Development Setup ```env theme={null} # Use localhost NEXT_PUBLIC_APP_URL=http://localhost:3000 # Use test/sandbox keys for payments STRIPE_SECRET_KEY=sk_test_... FLUTTERWAVE_SECRET_KEY=FLWSECK_TEST-... ``` ### Production Setup ```env theme={null} # Use your actual domain NEXT_PUBLIC_APP_URL=https://yourdomain.com # Use live/production keys STRIPE_SECRET_KEY=sk_live_... FLUTTERWAVE_SECRET_KEY=FLWSECK-... ``` ## Deployment Platforms Different platforms have different ways to set environment variables: ### Vercel 1. Go to your project settings 2. Navigate to **Environment Variables** 3. Add each variable with appropriate environment (Production/Preview/Development) 4. Redeploy to apply changes ### Railway 1. Open your project 2. Go to **Variables** tab 3. Add each variable 4. Changes apply automatically on next deployment ### Docker Use an `.env` file or pass variables in `docker-compose.yml`: ```yaml theme={null} services: app: environment: - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} # ... other variables ``` ## Security Best Practices 1. **Never commit** `.env.local` or `.env` files to version control 2. **Use different keys** for development and production 3. **Rotate keys regularly**, especially after team member changes 4. **Use secrets management** in production (AWS Secrets Manager, etc.) 5. **Limit key permissions** to only what's needed 6. **Monitor usage** of API keys for unusual activity ## Troubleshooting ### Variables not loading 1. Restart the development server after changing `.env.local` 2. Ensure the file is in the correct location: `apps/lms/.env.local` 3. Check for syntax errors (no spaces around `=`) ### Public variables undefined in browser Make sure they start with `NEXT_PUBLIC_` and restart the dev server. ### AWS/Stripe/SendGrid not working Verify: 1. Keys are correct and not expired 2. Services are enabled in their respective dashboards 3. For Stripe: webhook endpoints are configured 4. For SendGrid: sender email is verified ## Next Steps After configuring your environment variables: 1. [Set up your database](/guides/database-setup) 2. [Deploy to production](/guides/deployment) 3. [Customize your platform](/guides/customization) # Self-Hosting Guide Source: https://docs.easelms.org/guides/self-hosting Complete guide to self-hosting EaseLMS on your own infrastructure with full control This guide covers self-hosting EaseLMS on your own server infrastructure, giving you complete control over your data and deployment. ## Why Self-Host? Self-hosting EaseLMS provides: * Complete control over your data and infrastructure * No vendor lock-in or platform dependencies * Custom security and compliance requirements * Cost optimization for large-scale deployments * Ability to modify and extend the platform For most users, we recommend using managed platforms like Vercel or Railway. Self-hosting requires more technical expertise. ## System Requirements ### Minimum Requirements * **CPU**: 2 cores * **RAM**: 4GB * **Storage**: 20GB SSD * **OS**: Ubuntu 20.04+ or Debian 11+ * **Node.js**: 18.0 or higher * **Network**: Public IP address ### Recommended for Production * **CPU**: 4+ cores * **RAM**: 8GB+ * **Storage**: 50GB+ SSD * **Bandwidth**: Unlimited or high limit * **Load Balancer**: For high availability ## Deployment Options Choose your deployment method: 1. **Docker Compose** - Easiest, recommended for most 2. **PM2 with Node.js** - Direct deployment 3. **Kubernetes** - For enterprise scale ## Option 1: Docker Deployment The recommended approach for self-hosting. ### Prerequisites ```bash theme={null} # Update package index sudo apt update # Install Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # Add user to docker group sudo usermod -aG docker $USER # Install Docker Compose sudo apt install docker-compose ``` ```bash theme={null} docker --version docker-compose --version ``` ### Setup Application ```bash theme={null} git clone https://github.com/enyojoo/easelms.git cd easelms ``` Create `apps/lms/Dockerfile`: ```dockerfile theme={null} FROM node:18-alpine AS base # Dependencies FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci # Builder FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build # Runner FROM base AS runner WORKDIR /app ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT 3000 CMD ["node", "server.js"] ``` Create in project root: ```yaml theme={null} version: '3.8' services: app: build: context: ./apps/lms dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=production - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} - AWS_REGION=${AWS_REGION} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME} - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY} - SENDGRID_API_KEY=${SENDGRID_API_KEY} - SENDGRID_FROM_EMAIL=${SENDGRID_FROM_EMAIL} restart: unless-stopped networks: - easelms networks: easelms: driver: bridge ``` Create `.env` in project root with all your environment variables: ```env theme={null} NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... SUPABASE_SERVICE_ROLE_KEY=eyJ... NEXT_PUBLIC_APP_URL=https://yourdomain.com # ... add all other variables ``` ```bash theme={null} # Build and start docker-compose up -d # View logs docker-compose logs -f app # Check status docker-compose ps ``` ### Docker Management Commands ```bash theme={null} # Stop application docker-compose down # Restart application docker-compose restart # Update application git pull docker-compose build docker-compose up -d # View logs docker-compose logs -f # Remove everything (including volumes) docker-compose down -v ``` ## Option 2: PM2 Deployment Direct Node.js deployment with PM2 process manager. ### Prerequisites ```bash theme={null} # Install Node.js 18 curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs # Verify node --version npm --version ``` ```bash theme={null} sudo npm install -g pm2 ``` ### Deploy Application ```bash theme={null} git clone https://github.com/enyojoo/easelms.git cd easelms npm install ``` Create `apps/lms/.env.local` with all your variables. ```bash theme={null} cd apps/lms npm run build ``` Create `ecosystem.config.js`: ```javascript theme={null} module.exports = { apps: [{ name: 'easelms', cwd: './apps/lms', script: 'npm', args: 'start', env: { NODE_ENV: 'production', PORT: 3000 }, instances: 'max', exec_mode: 'cluster', autorestart: true, watch: false, max_memory_restart: '1G', }] }; ``` ```bash theme={null} pm2 start ecosystem.config.js pm2 save pm2 startup ``` ### PM2 Management Commands ```bash theme={null} # View status pm2 status # View logs pm2 logs easelms # Restart pm2 restart easelms # Stop pm2 stop easelms # Monitor pm2 monit # Update application git pull cd apps/lms npm install npm run build pm2 restart easelms ``` ## Nginx Reverse Proxy Set up Nginx as a reverse proxy for better performance and SSL. ```bash theme={null} sudo apt update sudo apt install nginx ``` Create `/etc/nginx/sites-available/easelms`: ```nginx theme={null} server { listen 80; server_name yourdomain.com www.yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Increase upload size for videos client_max_body_size 500M; } ``` ```bash theme={null} sudo ln -s /etc/nginx/sites-available/easelms /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl restart nginx ``` ```bash theme={null} sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com ``` ## Database Setup While we recommend using Supabase's hosted PostgreSQL, you can self-host the database too. Self-hosting the database requires significant expertise in PostgreSQL administration, backups, and security. ### Self-Hosted PostgreSQL (Optional) ```bash theme={null} # Install PostgreSQL sudo apt install postgresql postgresql-contrib # Create database and user sudo -u postgres psql CREATE DATABASE easelms; CREATE USER easelms_user WITH PASSWORD 'strong_password'; GRANT ALL PRIVILEGES ON DATABASE easelms TO easelms_user; \q ``` Then modify your Supabase connection strings to point to your local database. ## Security Hardening ### Firewall Configuration ```bash theme={null} # Install UFW sudo apt install ufw # Allow SSH sudo ufw allow 22 # Allow HTTP and HTTPS sudo ufw allow 80 sudo ufw allow 443 # Enable firewall sudo ufw enable ``` ### Automatic Security Updates ```bash theme={null} sudo apt install unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades ``` ### Fail2Ban for SSH Protection ```bash theme={null} sudo apt install fail2ban sudo systemctl enable fail2ban sudo systemctl start fail2ban ``` ## Monitoring and Logging ### Set Up Monitoring ```bash theme={null} # Install htop for resource monitoring sudo apt install htop # Monitor with PM2 (if using PM2) pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 10M pm2 set pm2-logrotate:retain 7 ``` ### Application Logs ```bash theme={null} # Docker logs docker-compose logs -f --tail=100 # PM2 logs pm2 logs --lines 100 # Nginx logs sudo tail -f /var/log/nginx/access.log sudo tail -f /var/log/nginx/error.log ``` ## Backup Strategy ### Automated Backups Create backup script `/home/user/backup.sh`: ```bash theme={null} #!/bin/bash BACKUP_DIR="/home/user/backups" DATE=$(date +%Y%m%d_%H%M%S) # Create backup directory mkdir -p $BACKUP_DIR # Backup application files tar -czf $BACKUP_DIR/easelms_$DATE.tar.gz /home/user/easelms # Backup database (if self-hosted) pg_dump -U easelms_user easelms > $BACKUP_DIR/db_$DATE.sql # Keep only last 7 days of backups find $BACKUP_DIR -type f -mtime +7 -delete echo "Backup completed: $DATE" ``` Set up cron job: ```bash theme={null} crontab -e # Add: Daily backup at 2 AM 0 2 * * * /home/user/backup.sh ``` ## Performance Optimization ### Enable Gzip in Nginx Add to `/etc/nginx/nginx.conf`: ```nginx theme={null} gzip on; gzip_vary on; gzip_min_length 10240; gzip_proxied expired no-cache no-store private auth; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json; ``` ### Enable Caching In Nginx configuration: ```nginx theme={null} location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; } ``` ## Scaling Considerations ### Horizontal Scaling For high traffic: 1. Deploy multiple instances behind a load balancer 2. Use Redis for session storage 3. Use CDN for static assets 4. Consider container orchestration (Kubernetes) ### Database Optimization * Enable connection pooling * Add read replicas for read-heavy workloads * Regular VACUUM and ANALYZE operations * Monitor slow queries ## Troubleshooting ### Application won't start ```bash theme={null} # Check logs docker-compose logs # or pm2 logs # Verify environment variables env | grep NEXT_PUBLIC # Check port availability sudo netstat -tlnp | grep 3000 ``` ### High memory usage ```bash theme={null} # Monitor resources htop # For Docker docker stats # Restart application docker-compose restart # or pm2 restart easelms ``` ### SSL certificate issues ```bash theme={null} # Test Nginx configuration sudo nginx -t # Renew certificate sudo certbot renew # Check certificate status sudo certbot certificates ``` ## Maintenance ### Regular Updates ```bash theme={null} # Update system packages sudo apt update && sudo apt upgrade # Update application cd easelms git pull npm install npm run build docker-compose restart # or pm2 restart easelms ``` ### Health Checks Create monitoring script: ```bash theme={null} #!/bin/bash URL="http://localhost:3000" RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $URL) if [ $RESPONSE -ne 200 ]; then echo "Application is down! Response: $RESPONSE" # Send alert (email, Slack, etc.) pm2 restart easelms fi ``` ## Next Steps After self-hosting: 1. Set up monitoring and alerting 2. Configure automated backups 3. Test disaster recovery procedures 4. Optimize performance based on usage 5. Plan for scaling as you grow Need help with self-hosting? Join our community or consider our [hosted service](https://www.easelms.org/hosted) for a managed solution. # Installation guide Source: https://docs.easelms.org/installation Self-host EaseLMS with complete control over your data and infrastructure ## Self-host EaseLMS This guide will walk you through setting up EaseLMS on your own infrastructure. You'll have complete control over your data, users, and platform customization. Don't want to manage infrastructure? Check out our [hosted service](https://www.easelms.org/hosted) for a fully managed solution. ## Prerequisites Before you begin, ensure you have the following: Download from [nodejs.org](https://nodejs.org/) Included with Node.js installation Sign up at [supabase.com](https://supabase.com) (free tier available) For cloning the repository **Optional services:** * **AWS account** - For S3 file storage (optional for development) * **SendGrid account** - For email notifications * **Stripe account** - For accepting payments globally * **Flutterwave account** - For African market payments ## Installation steps Clone the EaseLMS repository from GitHub: ```bash theme={null} git clone https://github.com/enyojoo/easelms.git cd easelms ``` Install all required packages using npm: ```bash theme={null} npm install ``` EaseLMS is a monorepo built with Turborepo. This command installs dependencies for both the LMS app and the marketing website. Create a `.env.local` file in the `apps/lms/` directory: ```bash theme={null} cd apps/lms touch .env.local ``` Add the following environment variables: ```bash Required - Supabase theme={null} # Supabase Configuration NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key ``` ```bash Optional - AWS S3 Storage theme={null} # AWS S3 Configuration (optional for development) AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_aws_access_key AWS_SECRET_ACCESS_KEY=your_aws_secret_key AWS_S3_BUCKET_NAME=your_bucket_name AWS_CLOUDFRONT_DOMAIN=your_cloudfront_domain ``` ```bash Optional - Payment Gateways theme={null} # Stripe Configuration STRIPE_SECRET_KEY=your_stripe_secret_key NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=your_stripe_publishable_key # Flutterwave Configuration FLUTTERWAVE_SECRET_KEY=your_flutterwave_secret_key NEXT_PUBLIC_FLUTTERWAVE_PUBLIC_KEY=your_flutterwave_public_key ``` ```bash Optional - Email & Currency theme={null} # SendGrid Email Service SENDGRID_API_KEY=your_sendgrid_api_key SENDGRID_FROM_EMAIL=noreply@yourdomain.com SENDGRID_FROM_NAME=EaseLMS SENDGRID_REPLY_TO=support@yourdomain.com # Currency Exchange Rates (uses exchangerate-api.com) EXCHANGERATE_API_KEY=your_exchangerate_api_key # Application URL NEXT_PUBLIC_APP_URL=http://localhost:3000 ``` Keep your `.env.local` file secure and never commit it to version control. It contains sensitive credentials. Set up your Supabase database: 1. Go to [supabase.com](https://supabase.com) and sign in 2. Click "New Project" 3. Choose your organization 4. Enter project details: * **Name** - Your project name (e.g., "easelms-production") * **Database Password** - Strong password for database access * **Region** - Choose the closest region to your users 5. Click "Create new project" and wait \~2 minutes for provisioning Supabase free tier includes 500MB database storage, 1GB file storage, and 2GB bandwidth - perfect for getting started. Retrieve your Supabase API credentials: 1. Navigate to **Project Settings** → **API** 2. Copy the following values: * **Project URL** → `NEXT_PUBLIC_SUPABASE_URL` * **anon/public key** → `NEXT_PUBLIC_SUPABASE_ANON_KEY` * **service\_role secret key** → `SUPABASE_SERVICE_ROLE_KEY` 3. Update your `.env.local` file with these values The service\_role key has full admin access to your database. Keep it secure and never expose it in client-side code. Set up your database schema: 1. In your Supabase project, go to **SQL Editor** 2. Click **New Query** 3. Open the migration file at `apps/lms/supabase/migrations/database_setup.sql` 4. Copy the entire file contents 5. Paste into the SQL Editor 6. Click **Run** (or press Cmd/Ctrl + Enter) 7. Wait for "Success. No rows returned" confirmation This migration creates all necessary tables: * `profiles` - User profiles linked to Supabase Auth * `courses` - Course information and settings * `lessons` - Individual lessons within courses * `enrollments` - Student course enrollments * `progress` - Lesson completion tracking * `payments` - Payment records and receipts * `certificates` - Generated course certificates * `instructors` - Instructor profiles * `resources` - Downloadable course materials * `quiz_questions` - Quiz questions and answers * `quiz_settings` - Quiz configuration * `quiz_attempts` - Student quiz attempts * `quiz_results` - Quiz scores and results * `course_instructors` - Links courses to instructors * `course_prerequisites` - Course dependencies * `lesson_resources` - Links lessons to resources * `platform_settings` - Global platform configuration Confirm your tables were created successfully: 1. Go to **Table Editor** in Supabase 2. You should see all 17 tables listed 3. Click on any table to view its structure The migration also creates triggers for automatically updating timestamps and creating user profiles on signup. From the root directory, start the development server: ```bash theme={null} npm run dev ``` This starts both applications: * **LMS Application:** [http://localhost:3000](http://localhost:3000) * **Marketing Website:** [http://localhost:3001](http://localhost:3001) ```bash Start only LMS app theme={null} cd apps/lms npm run dev ``` ```bash Start only website theme={null} cd apps/website npm run dev ``` Open your browser and navigate to [http://localhost:3000](http://localhost:3000). You'll be redirected to `/auth/learner/login` - the learner login page. ## Create your first admin user After installation, you need to create an admin account to access the admin dashboard. 1. Navigate to [http://localhost:3000](http://localhost:3000) 2. You'll be redirected to the login page 3. Click "Sign Up" to create a new account 4. Fill in your details and submit By default, new users are created with the "user" (learner) role. To make your account an admin: 1. Go to your Supabase project → **Table Editor** 2. Open the `profiles` table 3. Find your user record by email 4. Change the `user_type` from `user` to `admin` 5. Save the changes 1. Log out and log back in 2. Navigate to `/admin` to access the admin dashboard 3. You'll now have access to: * User management * Course creation and editing * Analytics and reports * Payment management * Platform settings Only grant admin access to trusted users. Admins have full control over the platform including user data, courses, and payments. ## Configure platform branding Customize your EaseLMS instance with your own branding. As an admin, go to **Settings** → **Brand** in the admin dashboard. Configure your platform details: **Basic information:** * **Platform name** - Your LMS name (appears in sidebar, emails) * **Platform description** - Brief description for metadata * **Contact email** - Support email shown to users * **App URL** - Your domain (used in emails and links) **Visual branding:** * **Logo (light mode)** - Logo for light theme * **Logo (dark mode)** - Logo for dark theme * **Favicon** - Browser tab icon **SEO metadata:** * **SEO title** - Browser title and search engine display * **SEO description** - Meta description for search engines * **SEO keywords** - Comma-separated keywords * **SEO image** - Social media preview image Upload your brand assets: 1. Click the upload button for each asset type 2. Select your image file 3. Wait for upload to AWS S3 (if configured) or Supabase Storage 4. Preview the changes in real-time **Recommended sizes:** * Logo: 120x40px (transparent PNG) * Favicon: 32x32px or 64x64px * SEO image: 1200x630px (JPG or PNG) Click "Save Changes" to apply your branding across: * Application UI (sidebar logo) * Email templates (logo and platform name) * Browser tabs (favicon) * Search engines (SEO metadata) ## Set up email notifications Enable automated email notifications for enrollments, completions, and payments. 1. Sign up at [sendgrid.com](https://sendgrid.com) 2. Verify your email address 3. Complete the SendGrid onboarding 1. Navigate to **Settings** → **API Keys** 2. Click **Create API Key** 3. Name it (e.g., "EaseLMS Production") 4. Select **Full Access** permissions 5. Click **Create & View** 6. Copy the API key (shown only once) Add SendGrid settings to your `.env.local`: ```bash theme={null} SENDGRID_API_KEY=SG.xxxxxxxxxxxxx SENDGRID_FROM_EMAIL=noreply@yourdomain.com SENDGRID_FROM_NAME=Your Platform Name SENDGRID_REPLY_TO=support@yourdomain.com ``` Use a verified sender email. SendGrid requires domain verification for production sending. 1. In SendGrid, go to **Settings** → **Sender Authentication** 2. Choose **Domain Authentication** (recommended) or **Single Sender Verification** 3. Follow the verification steps 4. Wait for verification to complete Restart your development server and test: 1. Create a new user account 2. Check your inbox for the welcome email 3. Enroll in a course and verify enrollment confirmation **Emails automatically sent:** * Welcome email (new user signup) * Enrollment confirmation * Course completion notification * Certificate ready notification * Payment confirmation/failure * Admin notifications (enrollments, payments, completions) ## Configure payment gateways Accept payments for paid courses using Stripe or Flutterwave. 1. Sign up at [stripe.com](https://stripe.com) 2. Complete business verification 3. Activate your account 1. Navigate to **Developers** → **API keys** 2. Copy your **Publishable key** and **Secret key** 3. Use test keys for development, live keys for production Add to `.env.local`: ```bash theme={null} STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx ``` 1. In Stripe Dashboard, go to **Developers** → **Webhooks** 2. Click **Add endpoint** 3. Set URL to `https://yourdomain.com/api/webhooks/stripe` 4. Select events: `checkout.session.completed`, `checkout.session.expired` 5. Copy the webhook signing secret 6. Add to `.env.local`: `STRIPE_WEBHOOK_SECRET=whsec_xxxxx` 1. Sign up at [flutterwave.com](https://flutterwave.com) 2. Complete business verification 3. Activate your account 1. Navigate to **Settings** → **API Keys** 2. Copy your **Public key** and **Secret key** 3. Use test keys for development Add to `.env.local`: ```bash theme={null} FLUTTERWAVE_SECRET_KEY=FLWSECK_TEST-xxxxxxxxxxxxx NEXT_PUBLIC_FLUTTERWAVE_PUBLIC_KEY=FLWPUBK_TEST-xxxxxxxxxxxxx ``` 1. In Flutterwave Dashboard, go to **Settings** → **Webhooks** 2. Set URL to `https://yourdomain.com/api/webhooks/flutterwave` 3. Copy the webhook secret hash 4. Add to `.env.local`: `FLUTTERWAVE_WEBHOOK_SECRET=xxxxx` ## Deploy to production When you're ready to deploy your EaseLMS instance: ```bash theme={null} npm run build ``` This creates optimized production builds for both apps. Deploy to your preferred platform: Easy deployment with zero config Full control with EC2, ECS, or Amplify Simple droplet or App Platform Configure all environment variables in your hosting platform: * Use production Supabase credentials * Use live Stripe/Flutterwave keys * Set production AWS S3 bucket * Update `NEXT_PUBLIC_APP_URL` to your domain 1. Add your custom domain to your hosting platform 2. Configure DNS records as instructed 3. Enable SSL/HTTPS (usually automatic) 4. Update platform branding settings with new domain ## Troubleshooting **Error:** "Could not connect to Supabase" **Solutions:** * Verify your `NEXT_PUBLIC_SUPABASE_URL` is correct * Check that your `NEXT_PUBLIC_SUPABASE_ANON_KEY` is valid * Ensure your Supabase project is active (not paused) * Check Supabase service status at [status.supabase.com](https://status.supabase.com) **Error:** Migration errors when running `database_setup.sql` **Solutions:** * Ensure you're running the migration in a fresh database * Check for syntax errors if you modified the migration * Run each section separately to identify the failing part * Check Supabase logs for detailed error messages **Error:** "Failed to upload file" **Solutions:** * Verify AWS credentials are correct * Check S3 bucket permissions allow uploads * Ensure CORS is configured on your S3 bucket * For development, uploads will fall back to Supabase Storage if AWS is not configured **Error:** Emails not being delivered **Solutions:** * Verify SendGrid API key is valid * Check sender email is verified in SendGrid * Review SendGrid activity logs for failures * Check spam folder for test emails * Ensure `SENDGRID_FROM_EMAIL` matches verified sender **Error:** Payment redirects fail **Solutions:** * Verify Stripe/Flutterwave API keys are correct * Use test mode keys for development * Check webhook endpoints are accessible * Review payment gateway dashboard for errors * Ensure `NEXT_PUBLIC_APP_URL` is set correctly for redirects ## Next steps Log in as admin and start building your course content Upload your logo and configure platform settings Add instructors to help create and manage courses Launch your LMS and start enrolling students ## Get help Need assistance with installation or configuration? Contact us for technical support Report bugs or request features Read the full documentation Skip setup with managed hosting # Introduction to EaseLMS Source: https://docs.easelms.org/introduction A modern, open-source Learning Management System built with Next.js, TypeScript, and Supabase for creating and delivering online courses EaseLMS Logo EaseLMS Logo ## The modern open-source Learning Management System EaseLMS is a complete solution for creating, managing, and delivering online courses with features like video lessons, interactive quizzes, progress tracking, certificates, and payment integration. Built with modern technologies, it provides a beautiful learning experience for students and powerful tools for instructors. Create your account and enroll in your first course in minutes Self-host EaseLMS with complete control over your data View the open-source code on GitHub Zero setup with our fully managed hosting solution ## Why choose EaseLMS? EaseLMS combines modern development practices with powerful learning features to deliver an exceptional experience for both learners and instructors. Built with Next.js 16, React 19, TypeScript, and Supabase for cutting-edge performance Modern, responsive design with dark mode support and mobile-first approach Built-in authentication, role-based access control, and data encryption Integrated payment processing with Stripe and Flutterwave Courses, quizzes, certificates, analytics, and comprehensive email notifications Complete control over your data and infrastructure ## Core features ### Course management Create unlimited courses with a rich content editor, video lessons with progress tracking, and document resources. Set up course prerequisites, organize content with categories and tags, and track enrollment analytics. EaseLMS supports multiple enrollment modes: free courses, one-time paid courses, and recurring subscriptions. **Enrollment options:** * **Free courses** - Open access for all learners * **Paid courses** - One-time purchase with Stripe or Flutterwave * **Recurring** - Subscription-based access * **Closed** - Private courses for specific learners ### Learning experience Learners enjoy a mobile-responsive course player with video playback, progress saving, and interactive quizzes. The platform tracks progress in real-time, awards achievement badges and certificates, and provides discussion features. **Supported lesson types:** * **Video lessons** - With progress tracking and Media Chrome player * **Text content** - Rich text with images and formatting * **Mixed lessons** - Combine video and text in a single lesson * **Interactive quizzes** - Multiple question types with instant feedback ### Admin and instructor tools Manage users across three roles (Admin, Instructor, Learner), view comprehensive analytics, and process payments. Customize your platform's branding including logos, colors, and SEO metadata. Role-based access control with Admin, Instructor, and Learner roles Track enrollments, course completion, revenue, and learner progress Platform name, logos (light/dark), favicon, and SEO metadata Automated emails via SendGrid for enrollments, completions, and payments ### Payment integration Accept payments globally with Stripe or target African markets with Flutterwave. The platform supports multi-currency, handles one-time and recurring payments, and maintains complete purchase history. **Payment features:** * Global payments via Stripe * African markets via Flutterwave * Multi-currency support with automatic conversion * Webhook handling for payment confirmations * Automated email receipts ### Certificates Automatically generate certificates when learners complete courses. Customize templates, add digital signatures, and allow PDF export. Certificates can be for completion, achievement, or participation. ## Technology stack ```typescript Frontend theme={null} // Built with Next.js 16 App Router // React 19 with TypeScript // Styled with Tailwind CSS // UI components from shadcn/ui ``` ```typescript Backend theme={null} // Supabase (PostgreSQL) // Supabase Auth for authentication // TanStack Query for state management // Server Actions for API ``` ```typescript Integrations theme={null} // AWS S3 + CloudFront for storage // Stripe & Flutterwave for payments // SendGrid for email notifications // PDFKit for certificate generation ``` **Core technologies:** * **Framework:** Next.js 16 with App Router * **Language:** TypeScript 5.0 * **Database:** Supabase (PostgreSQL) * **Authentication:** Supabase Auth * **Storage:** AWS S3 + CloudFront * **Payments:** Stripe & Flutterwave * **Email:** SendGrid * **Styling:** Tailwind CSS * **Components:** Radix UI + shadcn/ui ## Email notification system EaseLMS includes comprehensive email notifications powered by SendGrid, using your platform's branding configured in Settings. **User notifications:** * Welcome email when signing up * Enrollment confirmation * Course completion notification * Certificate ready notification * Payment confirmation and failure alerts **Admin notifications:** * New enrollment alerts * Payment received notifications * Course completion reports All emails automatically use your custom branding including platform name, logo, contact email, and support both light and dark mode. ## Deployment options Deploy EaseLMS on your own infrastructure with complete control. Requires Node.js 18+, Supabase account, and optional AWS S3 for file storage. **Benefits:** * Full control over data and infrastructure * Customize code to your needs * No recurring hosting fees (pay for infrastructure only) * AGPL-3.0 license for internal use [View installation guide →](/installation) Get started in minutes with our fully managed hosting solution. **Benefits:** * Zero infrastructure setup * Automatic backups and updates * Professional support included * Enterprise-grade security * Scalable infrastructure [Learn about hosted service →](https://www.easelms.org/hosted) ## Open source license EaseLMS is licensed under AGPL-3.0. You can freely use it for your organization, modify the code, and use it commercially. If you provide it as a hosted service (SaaS) with modifications, you must open-source your changes. If you want to create a proprietary hosted service without open-sourcing modifications, you'll need a commercial license. [Contact us](https://www.easelms.org/contact) for details. ## Next steps Sign up and enroll in your first course Set up EaseLMS on your own infrastructure View source code and contribute Contact us for help or questions # Quick start guide Source: https://docs.easelms.org/quickstart Get started with EaseLMS in minutes - from account creation to enrolling in your first course ## Get started with EaseLMS This guide will walk you through signing up for an account, browsing courses, and enrolling in your first course. You'll be learning in under 5 minutes. This guide is for learners using an existing EaseLMS instance. If you're setting up your own instance, see the [installation guide](/installation). ## Create your account Get started by creating your learner account on the EaseLMS platform. Visit your EaseLMS instance and click "Sign Up" or navigate directly to `/auth/learner/signup`. Provide your information: * **First name** - Your given name * **Last name** - Your family name * **Email** - Your email address (used for login) * **Password** - At least 8 characters * **Confirm password** - Must match your password ```typescript Signup fields theme={null} interface SignupForm { firstName: string lastName: string email: string password: string // min 8 characters confirmPassword: string acceptedTerms: boolean } ``` Check the box to agree to the Terms and Privacy Policy. You must accept the terms to create an account. Click "Sign Up" to create your account. Depending on the platform configuration: * **Email confirmation disabled** - You'll be immediately signed in and redirected to your dashboard * **Email confirmation enabled** - Check your inbox for a verification email before logging in ## Explore available courses Once you're logged in, you'll land on your learner dashboard. From here, you can explore and enroll in courses. Click "Courses" in the left sidebar or visit `/learner/courses` to view all available courses. Use the search and filter tools to find courses: **Search:** * Type in the search box to filter courses by title **Sort options:** * Relevance (default) * Title (A-Z) * Price: Low to High * Price: High to Low * Most Enrolled **Tabs:** * **All Courses** - Browse all available courses * **Enrolled** - View courses you're enrolled in * **Completed** - See courses you've finished Click on a course card or the "Preview" button to view details: * Course description and requirements * Lesson list and curriculum * Course instructors * Preview video (if available) * Enrollment type (Free, Paid, or Recurring) Course preview pages show all the information you need to decide if a course is right for you. ## Enroll in a course When you've found a course you want to take, enrolling is simple. From the course preview page or course card, click "Enroll". You'll be immediately enrolled and redirected to the course learning page at `/learner/courses/[course-slug]/learn`. The first lesson will load automatically. From the course card or preview page, click "Buy" to purchase the course. You'll be redirected to the payment gateway (Stripe or Flutterwave) to complete your purchase. **Supported payment methods:** * Credit/debit cards (Stripe) * Bank transfers (Flutterwave) * Mobile money (Flutterwave - African markets) Prices are automatically converted to your preferred currency based on your profile settings. After successful payment: * You'll be redirected back to the courses page * You'll receive a payment confirmation email * Your enrollment will be activated automatically * The admin will receive a payment notification Navigate back to the course and click "Continue" to begin your first lesson. If payment fails or is canceled, you'll be redirected to the courses page with an error message. You can try again when ready. ## Take your first lesson Once enrolled, you can start learning immediately. From your dashboard or courses page, click "Continue" on your enrolled course. You'll be taken to `/learner/courses/[course-slug]/learn`. The course learning interface includes: **Left sidebar:** * Course title and progress * Complete lesson list with checkmarks for completed lessons * Click any lesson to navigate (if sequential progress is not required) **Main content area:** * Video player for video lessons * Text content for text lessons * Combined content for mixed lessons * Quiz interface for quiz lessons **Bottom navigation:** * "Previous" and "Next" buttons to move between lessons ```typescript Lesson types theme={null} type LessonType = | 'video' // Video with Media Chrome player | 'text' // Rich text content | 'mixed' // Video + text combined ``` **For video lessons:** * The video player automatically tracks your progress * Progress is saved in real-time as you watch * Resume from where you left off when you return **For text lessons:** * Scroll through the content * Click "Mark as Complete" when finished Your progress is saved automatically and synced in real-time across devices. Some lessons include quizzes to test your knowledge: 1. Read each question carefully 2. Select your answer(s) 3. Click "Submit Answer" to check if you're correct 4. Review explanations for incorrect answers 5. Complete all questions to mark the lesson as done **Quiz question types:** * Multiple choice (single answer) * Multiple select (multiple answers) * True/false Some courses require a minimum quiz score to progress. Check the course settings for requirements. If the lesson includes downloadable resources: * Click the "Resources" tab in the lesson view * Download PDFs, documents, or other materials * Use resources to supplement your learning ## Track your progress Monitor your learning journey from your dashboard. Navigate to `/learner/dashboard` to see: * **Courses in Progress** - Number of enrolled courses you're actively taking * **Completed Courses** - Number of courses you've finished The "Continue Learning" card shows: * All courses you're enrolled in but haven't completed * Progress percentage for each course * "Continue" button to resume from your last lesson ```typescript Progress calculation theme={null} // Progress is calculated as: const progress = Math.round( (completedLessons / totalLessons) * 100 ) // A course is complete when progress === 100% ``` Discover new courses in the "Recommended Courses" section based on: * Available courses you haven't enrolled in * Platform recommendations * Your learning history ## Complete a course and earn your certificate When you finish all lessons, you can access your certificate (if enabled for the course). Work through every lesson in the course until your progress reaches 100%. Some courses require completing lessons sequentially. Others allow you to jump between lessons freely. Once complete, click "View Summary" from the course card or navigate to `/learner/courses/[course-slug]/learn/summary`. The summary page shows: * Course completion status * Your quiz scores (if applicable) * Time spent on the course * Certificate download (if enabled) If certificates are enabled for the course: 1. Click "Download Certificate" on the summary page 2. Your certificate will be generated as a PDF 3. You'll receive a "Certificate Ready" email notification **Certificate types:** * **Completion** - For finishing all course content * **Achievement** - For meeting performance criteria * **Participation** - For active engagement ## Manage your account Update your profile and preferences at any time. Click your profile icon in the top right or visit `/learner/profile`. Edit your: * Name and profile picture * Email address * Password * Preferred currency for pricing * Bio and other details If you've purchased any courses, view your payment history and receipts in the profile section. ## Next steps Explore all available courses and find your next learning opportunity Track your progress and continue your learning journey Install EaseLMS on your own infrastructure Contact us if you need help