Cleans a single path segment (no /): whitespace runs become a single -, unsafe ASCII is
dropped, runs of - collapse to one, and leading/trailing - are trimmed.
Decode each maximal run of %XX escapes as one UTF-8 unit, dropping a run that isnโt valid
UTF-8 (rather than emitting U+FFFD). Non-escape characters pass through. Mirrors the migrationโs
safe_percent_decode, so storage and lookup agree on inputs like /%FF.
Looks up a non-deleted page in course_id whose stored url_path matches url_path in
either the decoded-canonical or the legacy fully-encoded form (see
url_path_lookup_candidates). Shared by the page lookup and its tests so both exercise
the same candidate-matching logic.
Re-keys a custom peer-review config and its questions onto new_exercise_id with config and
question ids from new_id, producing an independent copy for a duplicated exercise so it does
not collapse onto the original exerciseโs config (#148). All questions must belong to
config.
Like get_chapter_pages, projected down to id and url_path for callers that only rewrite
paths and would otherwise pull every pageโs content JSONB for nothing.
Names of the exercise services that only global admins can add which saving exercise_tasks
would add: a task counts as added unless it is the first submitted task with its id and a task
with that id and type already exists where existing says.
Like get_page, but projects only the columns the chatbot needs and folds in the chapter name,
so the caller does not also have to fetch the whole page (including content) or the chapter.
Creates the snapshotโs page empty under its id, or brings back the deleted page with that id,
so that update_page can write its content and exercises next. Writes no page history: the
content save that follows does. Errors with PreconditionFailed if the page belongs to another
course.
Whether ch is an ASCII char that URL_PATH_ENCODE_SET would encode. The setโs membership
test is crate-private, so probe it via utf8_percent_encode (which encodes an ASCII char iff
itโs in the set). ASCII-only: the caller keeps non-ASCII verbatim.
Legacy lookup candidate: unsafe ASCII percent-encoded, non-ASCII kept decoded โ the form used
before paths were stored stripped. Lets pages written that way (notably exam pages, which the
cleanup migration skips) still resolve.
Canonical storage/lookup form: decode %xx, then per /-segment turn whitespace into -,
strip the unsafe ASCII in URL_PATH_ENCODE_SET, and collapse/trim dashes. Case, non-ASCII and
/, -, ., _, ~ are kept. Unsafe chars are removed, not %xx-encoded, so every save
funnels through here and paths canโt re-accumulate escapes. Mirrors clean_url_path (page
migration) and the frontend cleanUrlPath; keep the three in agreement.
Gives questions fresh ids pointing at target_config_id when it differs from source_config_id
(i.e. they are being cloned onto a different config); otherwise returns them untouched so the
upsert reuses the existing rows.
Saves new chapter numbers, moving the chaptersโ page paths along with redirects from the old
ones, and records the reordered chapters in their history.
Makes the order numbers and chapter ids to match in the db whatโs in the page objects
Assumes that all pages belong to the given course id
Also assumes the list of pages includes all nondeleted pages in the course, except chapter front pages, which cannot be moved, otherwise we will end up with random order numbers
Moves pages to new paths, normalized as the page save stores them, and redirects each moved
course pageโs old path to it. Returns those redirects; pages already at their path, deleted
pages and exam pages get none.
With expected_updated_at set, refuses with ModelErrorType::StaleVersion unless the page row
still has that updated_at. The check runs under the page row lock.
Writes every field of the snapshot to the page through the CMS page save, creating the page
with the snapshotโs id first when is_create. The save records page history as author.
Returns the redirect from the pageโs old path when its path changed.
Stored url_path forms to try for a requested path: the strip-canonical form plus the two
legacy encoded forms, deduped (an ASCII-only safe path yields a single candidate).