Skip to content

Course Module Overview

The Course module handles everything related to our courses and the gamification of design education.

Hierarchy

Courses have a nested Hierarchy of sub-elements:

  • Course contains 1+ Lessons.
    • Each Lesson contains 1+ LessonItems.
      • Each LessonItem may contain zero or more Quizzes
        • Quiz has one Question of these types:
          • Multiple-Choice Question (MCQ)
          • Open-Ended Question (OEQ)

Course

A course can be created by an Instructor or a Course Manager (as defined in the Permission module). A created course will not be available for enrollment until it is published. A course may have one or more instructors, marked with a sequence number similar to article author sequences.

When a course is ready, it is scheduled to launch by creating a CourseSchedule. A course may be scheduled as many times as needed. By default, there is only one active CourseSchedule at a time, and another is automatically scheduled to start right after the current one ends. However, special CourseSchedules can be launched as required.

Course States

StateVisibleAuto-scheduleRanking
Draft🔴🔴🔴
Published🟢🟢🟢
DeprecatedPartially*🔴🟢
  • * Visible to enrolled Members only

Course Difficulty Levels

Courses have difficulty levels: beginner, intermediate, and advanced. The course level is defined by the instructor and is used to:

  1. Group courses (mainly for display) on the /courses page.
  2. Automatic course scheduling (see below).

Course Schedules and Auto-Scheduling

Each course can have an indefinite number of CourseSchedules, to which our members can enroll. This means that the course enrollment (CourseEnrollment) is linked to the CourseSchedule, not directly to the Course.

NOTE

The relation between Course and CourseEnrollment exists to enhance performance (denormalization).

There are currently two ways to create a new CourseSchedule:

  1. Scheduled Job: courses:schedule, which processes all auto-scheduled courses and creates new schedules.
  2. Course Admin Panel: Manually create schedules via the Course Admin Panel.

How and Why the App Schedules Courses

The application schedules courses to manage enrollment periods and control when course content becomes available. This allows for cohort-based learning experiences and staggered release of content.

CourseSchedule Model

The CourseSchedule model represents a specific offering of a course with defined start and end dates.

  • Attributes:
    • course_id: References the associated Course.
    • start_date: When the course schedule begins.
    • end_date: When the course schedule ends.
    • is_auto_scheduled: Indicates if the schedule was created automatically.

LessonSchedule Model

When a course is scheduled, each lesson is also scheduled using the LessonSchedule model, determining the availability of lessons.

  • Attributes:
    • lesson_id: References the associated Lesson.
    • course_schedule_id: Associates the lesson schedule with a CourseSchedule.
    • start_date: When the lesson becomes available to enrolled members.

Auto-scheduling Logic

This is the logic used to create the schedules for each course that is published and has auto-scheduling turned on:

  1. Retrieve the course schedule that has the highest close date
  2. If that course schedule close date is in the future, then do not create a new schedule
  3. If that course schedule close date is less than 3 days ago and the course level is beginner or advanced, do not create a new schedule
  4. If that course schedule close date is less than 2 days ago and the course level is intermediate, do not create a new schedule
  5. Starting from tomorrow, look for the first day that does not have any course of the same level closing on that day
  6. Create a new course schedule starting on the day found on step 5

The resulting effect of applying this logic is:

  1. There will be one, and only one, course of each level starting on each day
  2. As soon as the course closes, a new schedule is created
  3. There will be at most one schedule open for each course
  4. We wait some days after the course is closed in order to schedule it again
  5. There will always be at least one course closed for each level

Percent Booked

The course cards display a "XX% booked" label. This number is currently generated using a semi-random number generator.

Lesson and Lesson Scheduling

A Lesson is a container for multiple LessonItems and includes a description or introduction. Each core Lesson belongs to one and only one course and has a lesson number, which determines its sequence within the course.

When a course is scheduled, instances of LessonSchedule are created for each lesson, defining their availability to enrolled members.

Lesson Availability

Lessons can be made available either immediately or weekly:

  1. Immediate Lessons: Available right after enrollment (e.g., Lesson 0 and Lesson 1).
  2. Weekly Lessons: Become available each week, according to their sequence number.

Shared Lessons

Overview

By default, Lessons are associated with a single Course. From late 2025, we have a new feature of Shared Lessons that allows re-using the same Lessons in different Courses. It is created to make some Lessons skippable (when a user already completed the Lesson in another Course), that is why you can see in some places another name: "Skippable Lesson" — it's the same feature, just a different name.

So far we use Shared Lessons feature for Lesson Zero (L0) and Final Lesson (LF). However, it potentially can be extended to use for any Lesson (which is particularly good when we revamp the whole course but want to keep few Lessons as is).

What "Shared" Means

Only Lessons can be shared — there are no individually "shared quizzes" or "shared lesson items". When you encounter terms like "shared score", "shared quiz answer", or "shared lesson item", they always refer to content or progress that belongs to a Shared Lesson. Quizzes, LessonItems, and quiz answers become "shared" only because they live inside a Shared Lesson.

Shared Points

Shared Lessons don't just share content — they also share quiz answers and points across Courses.

How it works: When a Member correctly answers a quiz in a Shared Lesson, that answer is stored once as a "shared answer" (not tied to any specific Course Enrollment). Every Course that uses that Shared Lesson can then pick up those points.

When does synchronization happen? Points are synchronized automatically when a Member visits the Shared Lesson within a Course Enrollment. For example, if a Member completed quizzes in L0 of Course A, they will see those points appear in Course B only after they visit L0 in Course B.

Important notes:

  • Only correct answers are shared. If a Member answers a quiz incorrectly, that answer stays bound to the specific Course Enrollment and is not shared to other Courses.
  • If an enrollment-scoped answer exists for a quiz, it takes precedence over the shared answer.
  • Completed Course Enrollments can't receive shared points — the whole process on a completed enrollment is locked. If a Member completes a Course without visiting the Shared Lesson, those shared points are permanently missed. This can affect the Member's final score, certificate distinction, and rank.
  • Workaround for admins: To manually trigger synchronization for a Member, impersonate the Member and visit the Shared Lesson (e.g., L0 or LF) in the affected Course. This only works for active (not completed) enrollments.

LessonItem

A LessonItem is where the actual content of a lesson resides. Depending on the instructor, a lesson may contain one or more lesson items. A LessonItem may contain zero or more Quizzes.

Reusing LessonItems

LessonItems are created for specific lessons and can only be added to one lesson. If added to another lesson, a copy is created to allow independent updates.

Future Enhancements:

  1. Reusing LessonItems by Reference: Allow the same LessonItem to be referenced in multiple lessons.
  2. Skip Already-Completed LessonItems: Enable members to skip lesson items they have already completed in other courses.

Optional vs. Mandatory

LessonItems can be:

  • Mandatory: Contains quizzes; requires completion of all quizzes to mark as complete.
  • Optional: Does not contain quizzes; marked as complete after being viewed.

External Content Provider Types

Most LessonItems contain text, video, and images. Content can be provided by the LI itself or an associated external content provider:

  • article: displays the content of an article attached to the LessonItem.

When the External Content Provider is not specified, Lesson Item's content is used to provide the content (so-call normal Lesson Item type). It is possible to use different shortcodes inside the content of “normal” Lesson Items.

Attached Discussion

Technically, a Discussion can be attached to any type of LessonItem.

Course-specific Shortcodes

There are a few shortcodes exclusively available in courses:

  • Course Certificate (course_certificate): Used in the "Final Lesson" of a course.
  • Evaluation (course_evaluation): Displays the course evaluation form for feedback.
  • Peers Offline (peers_offline): Provides a discussion page for offline interactions.
  • Peers Online (peers_online): Shows related meetups, local groups, etc.
  • Self-Check Information (self_check_info): Prompts the member to update their profile as seen by other course-takers.
  • Next Steps (next_steps): Suggests next steps by build the career (ex-"growth").

Quizzes

A Quiz is mainly a pointer to a question and can be of two types:

  • Multiple-Choice Question (MCQ): Allows three answer choices, with one correct answer.
  • Open-Ended Question (OEQ): Requires a text/html answer, which is graded by a Course Grader.

Each quiz can be used in one LessonItem. Reusing quizzes in different LessonItems is not highly required but can be achieved by copying quizzes.

Quiz Answer

A Quiz Answer is an answer submitted by a member.

Quiz Answer Score

  • MCQs: Always a non-negative integer [0..].
  • Non-Graded OEQs: Always null.
  • Rejected OEQs: Always null.
  • Accepted OEQs: A natural number (0..].

Course Scores

Course scores distinguish between confirmed points and potential points (possible from ungraded OEQs).

  • Confirmed Points: Points actually earned from MCQs or graded OEQs.
  • Potential Points: Maximum possible points from ungraded OEQs.

Scores are stored on the QuizAnswer model. The sum of all scores is stored in the CourseEnrollment to enhance performance.

Recalculating Course Scores

Inconsistencies can occur due to changes in published courses (adding/removing quizzes or LessonItems). Course scores can be recalculated (currently using a Nova action) to resolve discrepancies.

Course Progress

As members answer quizzes, they progress through courses, lessons, and LessonItems. Progress is defined by the number of quizzes answered relative to the total number of quizzes at each level.

The CourseEnrollmentProgress and CourseEnrollment models keeps track of the number of quizzes answered with submission timestamps as metadata.

Fetching Course Progress

Fetching course progress requires aggregating data from multiple models. To optimize performance:

  • Eager Loading: Utilize Eloquent eager loading to minimize the number of queries.
  • Caching: Implement caching for computed progress percentages.
  • Denormalization: Store computed progress percentages on the CourseEnrollment model where appropriate.

Course Ranks

Course ranks are calculated against others for a whole course, not just a specific schedule.

  • Course Ranks: Display the member's rank on course and LessonItem pages.
  • Ranking Table: Shows ranks of the member and others, accessible via the rank icon on course and LessonItem pages.

Course Certificates

A course certificate is a document certifying that a member has successfully completed a course. It includes:

  • Member's name
  • Course title
  • Course start and end dates
  • Distinction
  • Estimated learning time

Members often share their certificates on social media platforms.

Sample Course Certificates

Sample certificates can be generated for each course to showcase the certificate appearance to prospective enrollees. These samples are localized per visitor's country, using randomly selected male or female names from a maintained list.

Career Programs

Career Programs provide a structured learning path for members to achieve specific career goals through a curated set of Courses and Master Classes. Members can enroll into multiple Career Programs and track their progress toward completion. The is_selected boolean field determines which enrolled program opens automatically when a logged-in member visits the Courses page.

Structure

A Career Program consists of:

  • Foundational Courses
  • Core Courses
  • AI Master Classes
  • Core Master Classes

Career Program Snapshots

When a member selects a Career Program, a snapshot is created that captures the exact Courses and Master Classes required at that moment. This ensures members can complete the program even if the structure of the Career Program changes later.

The snapshot stores:

  • List of foundational Course IDs
  • List of core Course IDs
  • List of AI Master Class IDs
  • List of core Master Class IDs
php
$snapshot = $careerProgram->makeSnapshot(); // Returns CareerProgramSnapshot value object

$snapshot->foundationalCourseIds(); // Returns an array of ids
$snapshot->coreCourseIds();  // Returns an array of ids
$snapshot->aiMasterclassIds();  // Returns an array of ids
$snapshot->coreMasterclassIds();  // Returns an array of ids

Member Journey

  1. Selection: Member selects a Career Program (only one can be selected at a time).

    php
    $member->selectCareerProgram($careerProgram);
    $member->getSelectedCareerProgram(); // Returns CareerProgram instance
  2. Progress Tracking: System tracks completion of Courses and Master Classes against the snapshot. The VerifyCareerProgramCompletionStatus action checks if all requirements are met:

    • All Courses (foundational + core) must be completed
    • All Master Classes (ai + core) must have certificates issued
  3. Completion: When all requirements are met, completed_at timestamp is set to the current time.

Display & Presentation

Career Program data is presented through the CareerProgramData DTO which includes:

  • Career Program name and description (with placeholder replacements)
  • URL of the Career Program, for example: /courses?careerProgram=learn-design-skills-to-become-valuable-in-any-job
  • Collections of foundational Courses, core Courses, AI Master Classes and core Master Classes

The GetCareerProgramData action retrieves all data needed to display Career Program, either from:

  • Existing Career Program snapshot
  • Current Career Program relations (for guests or non-selected programs)
php
$careerProgramDataForGuests = (new GetCareerProgramData())->execute($careerProgram);
$careerProgramDataForMember = (new GetCareerProgramData())->execute($careerProgram, $member);

Business Rules

  • Members can only have one selected Career Program at a time
  • Selecting a new program deselects the previous one automatically
  • Completion status is preserved even after deselecting a program
  • Programs use snapshots to ensure stable requirements for enrolled members

Key Classes

  • CareerProgram: Main model representing a Career Program
  • CareerProgramItem: Polymorphic model that represents Career Program item (course or Master Class)
  • CareerProgramMember: Pivot model tracking member enrollments to Career Programs
  • CareerProgramSnapshot: Value object storing Career Program snapshot

Events

The Course module dispatches several events to communicate with other modules:

php
use App\Modules\Course\Events\CourseDelete;

Event listeners in other modules can subscribe to these events to perform actions such as awarding badges or sending notifications.

Future Considerations

Event Sourcing

We are considering using Event Sourcing for this module to improve scalability and maintainability.

  • Benefits:
    • Better tracking of state changes.
    • Easier debugging and auditing.
    • Improved scalability for write-heavy operations.