This is the first delivered slice of the skilll platform. It covers the Instructor’s ability to create and edit a Course — the top-level learning unit in the domain.

What was built

The Instructor can now:

  • Create a Course with four required fields: title, description, price (€0 = free), and language
  • Set visibility to public or private
  • Save the Course as draft (the default state)
  • Edit any field after creation

The system enforces all four required fields and rejects blank submissions with live validation.


Domain model

erDiagram
    COURSE {
        int id
        string title
        string description
        int price
        string language
        string visibility
        string status
        datetime inserted_at
        datetime updated_at
    }

status is an enum: draft | published. visibility is public | private. Both are orthogonal — all four combinations are valid. Price = 0 is a valid free Course.


Architecture

flowchart LR
    subgraph Web
        LV[CourseLive.New / Edit]
        FC[FormComponent]
    end
    subgraph Domain
        CTX[Skilll.Courses context]
        SCH[Course schema + changeset]
    end
    subgraph DB
        PG[(PostgreSQL\ncourses table)]
    end

    LV --> FC --> CTX --> SCH --> PG

The LiveView is intentionally thin — it delegates all business rules to Skilll.Courses. The FormComponent is shared between New and Edit, receiving action: :new | :edit.


Screenshots

Empty state

Courses index — empty state

The index page before any Course exists.

New Course form

New Course form

Four required fields + visibility toggle. Price defaults to 0.

Validation

Validation errors

Submitting with blank title, description, or language shows inline errors. Price doesn't error because it defaults to 0.

Courses list

Courses list with two entries

After creating two courses: one free, one at €149. Price=0 renders as "Free".

Edit Course

Edit Course page

The edit page loads existing data. The header shows the current title and Draft badge.


Acceptance criteria

  • Create a Course with title, description, price, and language (all required)
  • Set visibility to public or private
  • Draft is the default state on creation
  • Edit Course fields after creation
  • System prevents saving if any required field is missing
  • Price = 0 is valid (free Course)

Key decisions

Price stored as integer cents — price is stored as a plain integer in PostgreSQL representing euro cents (e.g. 4900 = €49). Zero is a valid free Course. When Paddle comes in (PBI 0060), no migration is needed — Paddle also works in cents.

FormComponent shared between New and Edit — a single LiveComponent receives action: :new | :edit and dispatches to create_course/1 or update_course/2. No duplication in the web layer.


Next up: PBI 0015 — Build and Publish a Course (Chapters, Text Lessons, reordering, publish action).