ContentStudioModule serves posts that people take page by page. Two surfaces: /client/content-studio/* for the person taking a post, and /content-studio/* for staff. The admin screen is Content Studio; on a site, the Content Player is its client.
Datatypes: post (the content), post_progress (one per person per post), form_submission (answers sent in).
The post
A post's pages live inside the record: pages[] (id, title, summary, content HTML, type, duration, reviewers, conditions, answerKey) and an outline, toc[], of chapters holding pages. Access follows the same rules as a CRM form: accessMode (open · code · participants), accessCode, authenticationType (none · magic-link · code · password · email), startDate / endDate, participants. course holds dueInDays, layout (sidebar · steps) and navigation (sidebar · top · none).
Nothing about a person is worked out ahead of time: which items are open, why one is locked, % complete, chapter counts, step numbers and what comes next are computed on read from the post and the person's progress.
Conditions
A page or chapter opens when every condition holds. Each points at an earlier item:
check | Holds when |
|---|---|
done | the page (or every page in the chapter) is done |
approved | a reviewer approved it |
score | its score is at least min (0–100) |
watched | at least min % of its video or audio was watched |
percent | the chapter is at least min % complete |
A chapter's conditions apply to everything in it.
Customer routes
/client/content-studio/mineJWT/client/content-studio/:courseJWT/client/content-studio/:course/enrollJWT/client/content-studio/:course/items/:itemIdJWT/client/content-studio/:course/items/:itemId/progressJWT/client/content-studio/:course/items/:itemId/answerJWT/client/content-studio/:course/preview?key=JWT:course is the post's id or slug; every route takes an optional ?code= (access code or invitation). The caller is the customer (or signed-in user) on the request — the site's app token alone is no one.
- Only the published copy is served. Unpublished edits never reach these routes.
- Reading needs no one. The outline and any unlocked item answer without a signed-in person; with one, they include that person's progress and saved answer.
- Saving needs a person and an enrollment.
progressandansweranswer403Enroll in this course first untilenrollhas run. - The outline returns
course(title, summary, cover, layout, navigation, steps),enrollment,items[]— each withstatus(open·locked·done·pending-review·changes),lockReason,type,duration,step, and for chaptersdone/total— andnext. preview?key=is the author's preview, opened by the site with its app token alone: it answers only with a live key fromPOST /content-studio/:course/preview-key, good for that post in that org for one hour.progresstakes{ done, percent, score };percentonly ever goes up.answertakes{ values, done }. The answer is kept in oneform_submissionper item, owned by the progress record, and rewritten on each save;done: falsekeeps a draft. With ananswerKey, the answer is marked 0–100. A page withreviewersthen waits for review instead of finishing; an approved page can no longer be changed.
Staff routes
/content-studio/reviewJWT/content-studio/:course/reviewJWT/content-studio/enrollments/:idJWT/content-studio/enrollments/:id/review/:itemIdJWT/content-studio/enrollments/:id/withdrawJWT/content-studio/:course/previewJWT/content-studio/:course/preview-keyJWT/content-studio/:course/enrollmentsJWT/content-studio/:course/enrollmentsJWTAll are @StaffOnly() — a signed-in business user, never the site's app token (see how a request is authorized). They read the working draft, not the published copy.
- Review queue — everything waiting for a reviewer, oldest first, with the person and what they sent (
answer).review/:itemIdtakes{ status: approved | changes, note }; only the page's reviewers, or admins, may decide. - Enrollments —
POSTtakes{ emails }; someone without an account is matched by email when they sign in.GETis the roster, withstatus,search,pageandpageSize. - Preview — the post with every item open and nothing recorded; each item carries
opensWhen, the condition a learner would wait on.preview-keyreturns{ key, expiresAt }, a one-hour key the site's/__preview/post/<post>?key=opens it with.
post_progress
| Field | |
|---|---|
postId | The post's sk |
status | active · completed · withdrawn |
enrolledAt, dueAt, completedAt | dueAt from the post's dueInDays |
progress | Keyed by outline item: done, at, score, percent, answerRef, review (status pending · approved · changes, by, at, note) |
The person is the record's owner. It moves to completed when every page is done.