> ## Documentation Index
> Fetch the complete documentation index at: https://docs.terang.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# LMS Progress API Guide

> Retrieve platform-wide and category-scoped learner progress metrics

## Overview

The `lms-progress` API provides a unified endpoint for retrieving progress, enrollment, and financial metrics across the platform. The Teacher and Admin Dashboards use this data to display performance statistics.

The endpoint returns two datasets:

1. **Overall** — Aggregated metrics for the entire platform.
2. **Per category** — Detailed metrics broken down by course category.

***

## Authentication

The API supports two methods of authentication:

1. **API Key (Recommended for server-to-server)** — Pass your API key in the `x-api-key` header.
2. **Session Cookie (For frontend clients)** — Pass the browser session cookie.

### Method 1: Using an API Key (Recommended)

Generate an API Key in the **Kunci API** sidebar under the Teacher Dashboard. Once generated, include it in your HTTP requests using the `x-api-key` header:

```bash theme={null}
curl -H "x-api-key: lms_key_your_actual_key" \
     https://lms.terang.ai/api/lms-progress
```

### Method 2: Using Session Cookies (Development)

To test the endpoint locally or via curl using browser cookies:

1. Sign in to the LMS dashboard via your browser.
2. Open the browser's developer tools and copy the session cookie value (usually named `authjs.session-token` or similar).
3. Run the curl command below, replacing `<cookie-value>` with your token:

```bash theme={null}
curl -H "Cookie: authjs.session-token=<cookie-value>" \
     https://lms.terang.ai/api/lms-progress
```

### Requesting from the browser (Frontend)

When fetching the API from frontend React components, you must include credentials. This ensures the browser attaches session cookies to the request.

```javascript theme={null}
fetch('/api/lms-progress', {
  credentials: 'include' // Required to send cookies
})
```

***

## Frontend integration guide

Use these TypeScript interfaces and React hooks to integrate the progress metrics into your dashboard page.

### TypeScript type definitions

Define the structure of the API response to ensure strict type safety.

```typescript theme={null}
export interface LmsProgressResponse {
  success: boolean;
  data: {
    overall: OverallMetrics;
    perCategory: CategoryMetrics[];
  };
}

export interface OverallMetrics {
  totalDaftar: number; // Legacy
  totalBeli: number; // Legacy
  totalSelesai: number; // Legacy
  totalSertifikat: number; // Legacy
  totalClasses: number;
  totalCourses: number;
  totalStudents: number;
  completionRate: number;
  buyers30Days: number;
  revenue30Days: number;
  totalKum30Days: number;
  certificatesCount: number;
  peopleWithBadgeCount: number;
}

export interface CategoryMetrics {
  categoryId: number;
  categoryCode: string;
  categoryName: string;
  totalBeli: number; // Legacy
  totalSelesai: number; // Legacy
  totalSertifikat: number; // Legacy
  totalClasses: number;
  totalCourses: number;
  totalStudents: number;
  completionRate: number;
  buyers30Days: number;
  revenue30Days: number;
  totalKum30Days: number;
  certificatesCount: number;
  peopleWithBadgeCount: number;
}
```

### Code example: React fetch hook

This example shows a custom React state approach to fetch, handle errors, and manage loading states:

```tsx theme={null}
import { useState, useEffect } from 'react';
import { LmsProgressResponse, OverallMetrics } from '@/types/progress';

export function useLmsProgress() {
  const [data, setData] = useState<LmsProgressResponse | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function loadProgress() {
      try {
        setLoading(true);
        const res = await fetch('/api/lms-progress', {
          credentials: 'include',
        });
        
        if (!res.ok) {
          if (res.status === 401 || res.status === 403) {
            throw new Error('Access denied. Log in with an authorized account.');
          }
          throw new Error(`Server returned status: ${res.status}`);
        }

        const payload: LmsProgressResponse = await res.json();
        if (!payload.success) {
          throw new Error('Failed to load metrics data.');
        }

        setData(payload);
      } catch (err: any) {
        setError(err.message || 'An unknown error occurred.');
      } finally {
        setLoading(false);
      }
    }

    loadProgress();
  }, []);

  return { data, loading, error };
}
```

***

## Technical calculation logic

The backend uses Drizzle ORM to perform queries and aggregates. Here is how the complex metrics are processed:

### Completion rate calculation

To calculate the `completionRate`, the API computes the progress of each student in each enrolled course and averages them. It runs a SQL CTE (Common Table Expression) to perform the math:

1. **Calculate total chapters per course**:
   Counts all chapters within modules mapped to the course.
2. **Calculate completed chapters per student**:
   Counts unique records in `student_progress` where `completed = true`.
3. **Compute enrollment progress**:
   Divides the completed chapters by the total chapters. If a course has 0 chapters, progress defaults to `0`.
4. **Average the progress**:
   Calculates the average progress across all enrollments.

The SQL CTE query executed by the database:

```sql theme={null}
WITH course_chapters AS (
  SELECT m.course_id, COUNT(c.id)::float AS total_chapters
  FROM chapters c
  JOIN modules m ON c.module_id = m.id
  GROUP BY m.course_id
),
student_completed AS (
  SELECT sp.student_id, sp.course_id, COUNT(DISTINCT sp.chapter_id)::float AS completed_chapters
  FROM student_progress sp
  WHERE sp.completed = true
  GROUP BY sp.student_id, sp.course_id
),
enrollment_progress AS (
  SELECT 
    CASE 
      WHEN COALESCE(cc.total_chapters, 0) > 0 
      THEN (COALESCE(sc.completed_chapters, 0) / cc.total_chapters) * 100 
      ELSE 0 
    END as progress
  FROM student_enrollments se
  LEFT JOIN course_chapters cc ON se.course_id = cc.course_id
  LEFT JOIN student_completed sc ON se.student_id = sc.student_id AND se.course_id = sc.course_id
)
SELECT COALESCE(AVG(progress), 0)::float as "avgProgress"
FROM enrollment_progress;
```
