04 — API: Content Management
Endpoint untuk browsing dan mengelola konten pembelajaran (domain, topic, speaking content).
Public Content Endpoints
GET /content/domains
Ambil semua domain konten yang tersedia.
Access: Public
// Response 200
{
"success": true,
"data": [
{
"id": "dom_business",
"name": "Business English",
"nameId": "Bahasa Inggris Bisnis",
"description": "Komunikasi di lingkungan kerja dan profesional",
"icon": "💼",
"topicCount": 8,
"contentCount": 42
},
{
"id": "dom_daily",
"name": "Daily Conversation",
"nameId": "Percakapan Sehari-hari",
"description": "Komunikasi dalam kehidupan sehari-hari",
"icon": "🗣️",
"topicCount": 12,
"contentCount": 65
},
{
"id": "dom_travel",
"name": "Travel & Tourism",
"nameId": "Wisata & Perjalanan",
"description": "Berkomunikasi saat bepergian ke luar negeri",
"icon": "✈️",
"topicCount": 6,
"contentCount": 28
},
{
"id": "dom_academic",
"name": "Academic English",
"nameId": "Bahasa Inggris Akademik",
"description": "Presentasi, diskusi, dan penulisan akademik",
"icon": "🎓",
"topicCount": 5,
"contentCount": 20
}
]
}
GET /content/domains/:domainId/topics
Ambil semua topik dalam satu domain.
Access: Authenticated (filtered berdasarkan CEFR level user)
// Query: ?cefrLevel=b1 (opsional, default: dari profil user)
// Response 200
{
"success": true,
"data": [
{
"id": "top_meeting",
"name": "Meeting & Discussion",
"nameId": "Rapat & Diskusi",
"description": "Berpartisipasi dan memimpin rapat kerja",
"cefrMin": "a2",
"cefrMax": "c1",
"contentCount": 8,
"isEligible": true, // User B1 → dalam range A2-C1
"completedCount": 2 // Berapa konten yang sudah dikerjakan user
},
{
"id": "top_negotiation",
"name": "Negotiation",
"nameId": "Negosiasi",
"description": "Bernegosiasi harga, kontrak, dan kesepakatan",
"cefrMin": "b1",
"cefrMax": "c2",
"contentCount": 5,
"isEligible": true,
"completedCount": 0
},
{
"id": "top_executive",
"name": "Executive Communication",
"nameId": "Komunikasi Eksekutif",
"description": "Presentasi board-level dan strategic communication",
"cefrMin": "b2",
"cefrMax": "c2",
"contentCount": 4,
"isEligible": false, // User B1 → di bawah minimum B2
"lockedReason": "Butuh level B2 untuk mengakses topik ini"
}
]
}
GET /content/topics/:topicId/contents
Ambil semua speaking content dalam satu topik.
Access: Authenticated
// Query: ?mode=role_play&difficulty=intermediate
// Response 200
{
"success": true,
"data": [
{
"id": "sc_meetingDiscussion01",
"title": "Weekly Team Standup",
"titleId": "Standup Meeting Mingguan",
"mode": "role_play",
"difficulty": "intermediate",
"cefrMin": "a2",
"cefrMax": "b1",
"isEligible": true,
"estimatedMinutes": 5,
"timesUsed": 1240,
"avgScore": 72,
"userCompleted": false
},
{
"id": "sc_meetingDiscussion02",
"title": "Client Presentation Review",
"titleId": "Review Presentasi Klien",
"mode": "role_play",
"difficulty": "upper_intermediate",
"cefrMin": "b1",
"cefrMax": "b2",
"isEligible": true,
"estimatedMinutes": 7,
"timesUsed": 890,
"avgScore": 68,
"userCompleted": true,
"userBestScore": 78
}
]
}
GET /content/:contentId
Ambil detail satu speaking content.
Access: Authenticated
// Response 200
{
"success": true,
"data": {
"id": "sc_meetingDiscussion01",
"title": "Weekly Team Standup",
"titleId": "Standup Meeting Mingguan",
"description": "Latihan berpartisipasi dalam standup meeting mingguan tim engineering",
"mode": "role_play",
"difficulty": "intermediate",
"cefrMin": "a2",
"cefrMax": "b1",
"estimatedMinutes": 5,
"stimulus": {
"scenario": "You are a software engineer attending the weekly team standup. Share your progress update and discuss blockers with your tech lead.",
"aiRole": "Tech Lead",
"userRole": "Software Engineer",
"context": "Monday morning, sprint week 3. The deadline for the feature release is next Friday."
},
"topic": {
"id": "top_meeting",
"name": "Meeting & Discussion"
},
"domain": {
"id": "dom_business",
"name": "Business English"
},
"stats": {
"timesUsed": 1240,
"avgScore": 72
},
"userHistory": {
"timesAttempted": 1,
"bestScore": 78,
"lastAttemptAt": "2026-09-18T10:00:00Z"
}
}
}
Admin Content Endpoints
POST /admin/content/domains
Buat domain baru.
Access: Admin
// Request Body
{
"name": "Healthcare English",
"nameId": "Bahasa Inggris Kesehatan",
"description": "Komunikasi di lingkungan medis dan kesehatan",
"icon": "🏥"
}
// Response 201
{
"success": true,
"data": {
"id": "dom_healthcare",
...
}
}
POST /admin/content/topics
Buat topik baru dalam domain.
Access: Admin
// Request Body
{
"domainId": "dom_business",
"name": "Customer Service",
"nameId": "Layanan Pelanggan",
"description": "Menangani pertanyaan dan keluhan pelanggan",
"cefrMin": "a2",
"cefrMax": "b2"
}
POST /admin/content/speaking-contents
Buat speaking content baru.
Access: Admin
// Request Body
{
"topicId": "top_meeting",
"title": "Project Kickoff Meeting",
"titleId": "Rapat Awal Proyek",
"description": "Latihan memimpin rapat kickoff proyek baru",
"mode": "role_play",
"difficulty": "upper_intermediate",
"cefrMin": "b1",
"cefrMax": "b2",
"stimulusData": {
"scenario": "You are the project manager kicking off a new project...",
"aiRole": "Team Member",
"userRole": "Project Manager",
"context": "New e-commerce platform project, 6-month timeline"
}
}
PUT /admin/content/speaking-contents/:id
Update speaking content.
Access: Admin
DELETE /admin/content/speaking-contents/:id
Soft delete speaking content.
Access: Admin
POST /admin/content/upload-media
Upload gambar atau audio untuk content stimulus.
Access: Admin
POST /api/v1/admin/content/upload-media
Content-Type: multipart/form-data
file: <image/audio file> (max 20MB)
type: "image" | "audio"
// Response 201
{
"success": true,
"data": {
"url": "https://r2.eljoy.id/content/picture-office-02.jpg",
"type": "image",
"size": 245000,
"mimeType": "image/jpeg"
}
}