elearning/docs/api-docs/api_course_cloning.md

6.8 KiB

Course Cloning API

📋 Overview

API endpoint for cloning an entire course including all content, lessons, quizzes, and attachments.


🔧 Clone Course

Endpoint

POST /api/instructor/courses/:courseId/clone
Authorization: Bearer <instructor-token>
Content-Type: application/json

Request Body

{
  "new_title": {
    "th": "Python สำหรับผู้เริ่มต้น (ปรับปรุง 2026)",
    "en": "Python for Beginners (Updated 2026)"
  },
  "new_slug": "python-beginners-2026",
  "copy_settings": {
    "copy_chapters": true,
    "copy_lessons": true,
    "copy_quizzes": true,
    "copy_attachments": true,
    "copy_announcements": false
  }
}

Response 201

{
  "new_course_id": 25,
  "cloned_from_course_id": 1,
  "status": "DRAFT",
  "message": "Course cloned successfully",
  "cloned_content": {
    "chapters": 10,
    "lessons": 45,
    "quizzes": 10,
    "questions": 150,
    "attachments": 30,
    "total_duration_minutes": 1200
  },
  "next_steps": [
    "Edit content as needed",
    "Update videos if necessary",
    "Submit for approval when ready"
  ]
}

📝 What Gets Cloned

Copied:

  • Course info (title, description, price, thumbnail)
  • All chapters
  • All lessons (including video references)
  • All quizzes and questions
  • All attachments (files copied to new location)
  • Course settings (is_free, have_certificate)
  • Lesson prerequisites
  • Sort orders

NOT Copied:

  • Enrollments
  • Student progress
  • Reviews/ratings
  • Announcements (optional)
  • Approval status (new course = DRAFT)
  • Statistics

🎯 Use Cases

1. Update Videos

1. Clone course
2. Replace videos in new course
3. Submit for approval
4. Publish

2. Add Lessons

1. Clone course
2. Add new lessons/chapters
3. Submit for approval
4. Publish

3. Major Restructure

1. Clone course
2. Reorganize chapters/lessons
3. Update content
4. Submit for approval
5. Publish

🔒 Permissions

Who can clone:

  • Course owner (created_by)
  • Primary instructor (is_primary = true)
  • Admin

Restrictions:

  • Cannot clone archived courses
  • Cannot clone rejected courses
  • Can clone approved/published courses

📊 Clone Process

Backend Process:

async function cloneCourse(originalCourseId, newData) {
  // 1. Create new course
  const newCourse = await createCourse({
    ...originalCourse,
    title: newData.new_title,
    slug: newData.new_slug,
    status: 'DRAFT',
    created_by: currentUser.id
  });
  
  // 2. Clone chapters
  for (const chapter of originalChapters) {
    const newChapter = await createChapter({
      ...chapter,
      course_id: newCourse.id
    });
    
    // 3. Clone lessons
    for (const lesson of chapter.lessons) {
      const newLesson = await createLesson({
        ...lesson,
        chapter_id: newChapter.id
      });
      
      // 4. Clone attachments (copy files)
      for (const attachment of lesson.attachments) {
        await copyFileToS3(attachment.file_path, newPath);
        await createAttachment({
          ...attachment,
          lesson_id: newLesson.id,
          file_path: newPath
        });
      }
      
      // 5. Clone quizzes
      if (lesson.quiz) {
        const newQuiz = await createQuiz({
          ...lesson.quiz,
          lesson_id: newLesson.id
        });
        
        // 6. Clone questions
        for (const question of lesson.quiz.questions) {
          const newQuestion = await createQuestion({
            ...question,
            quiz_id: newQuiz.id
          });
          
          // 7. Clone choices
          for (const choice of question.choices) {
            await createChoice({
              ...choice,
              question_id: newQuestion.id
            });
          }
        }
      }
    }
  }
  
  return newCourse;
}

⚠️ Error Handling

Error: Course Not Found

{
  "error": {
    "code": "COURSE_NOT_FOUND",
    "message": "Course not found or you don't have permission"
  }
}

Error: Slug Already Exists

{
  "error": {
    "code": "SLUG_EXISTS",
    "message": "Course slug already exists. Please use a different slug.",
    "suggestion": "python-beginners-2026-v2"
  }
}

Error: Clone Failed

{
  "error": {
    "code": "CLONE_FAILED",
    "message": "Failed to clone course. Please try again.",
    "details": "Error copying attachment: file.pdf"
  }
}

🎨 Frontend Integration

Clone Button

async function cloneCourse(courseId) {
  const response = await fetch(`/api/instructor/courses/${courseId}/clone`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      new_title: {
        th: `${originalTitle.th} (คัดลอก)`,
        en: `${originalTitle.en} (Copy)`
      },
      new_slug: `${originalSlug}-copy-${Date.now()}`
    })
  });
  
  const data = await response.json();
  
  if (response.ok) {
    // Redirect to edit new course
    window.location.href = `/instructor/courses/${data.new_course_id}/edit`;
  }
}

📈 Performance Considerations

Large Courses:

  • Use background job for cloning
  • Show progress indicator
  • Send notification when complete
POST /api/instructor/courses/:courseId/clone
{
  "async": true
}

Response 202:
{
  "job_id": "clone-job-123",
  "status": "PROCESSING",
  "message": "Clone in progress. You will be notified when complete.",
  "estimated_time_minutes": 5
}

Check Job Status:

GET /api/instructor/clone-jobs/:jobId

Response:
{
  "job_id": "clone-job-123",
  "status": "COMPLETED",
  "new_course_id": 25,
  "progress": {
    "chapters": "10/10",
    "lessons": "45/45",
    "quizzes": "10/10"
  }
}

Get Clone History

GET /api/instructor/courses/:courseId/clones

Response:

{
  "original_course_id": 1,
  "clones": [
    {
      "id": 25,
      "title": "Python for Beginners (Updated 2026)",
      "status": "PUBLISHED",
      "cloned_at": "2026-01-06T15:00:00Z",
      "cloned_by": "Prof. John Smith"
    },
    {
      "id": 30,
      "title": "Python for Beginners (Advanced)",
      "status": "DRAFT",
      "cloned_at": "2026-01-05T10:00:00Z"
    }
  ]
}

Summary

Endpoint: POST /instructor/courses/:courseId/clone

Copies:

  • All chapters, lessons, quizzes
  • All attachments (files copied)
  • All settings and configurations

Does NOT copy:

  • Enrollments or student data
  • Reviews or ratings
  • Approval status

Result: New DRAFT course ready for editing


🚀 Next Steps

After cloning:

  1. Edit new course as needed
  2. Update videos if necessary
  3. Add/remove lessons
  4. Submit for approval
  5. Publish when approved