Catalog
The author catalog, the badges computed on read, and what a showcase exposes.
Import the domain from @workspace/training/domain/catalog and the services from @workspace/training/application/catalog. The catalog lets teachers browse by author. Every account has one, free: the collections it shares with students through the public link, and its public deals. The author page, discovery and subscriptions build on it.
Badges
An author carries up to two badges, shown together next to the display name. Both are computed on every read and never stored.
| Badge | When |
|---|---|
certified | accounts.certified, set by hand from the back-office (AccountsService.setCertified). Shown in blue. |
premium | A paid plan is running: at least one active grant with source: "subscription", whatever the plan. A grant written by hand does not count. Shown in gold, and gone as soon as the plan ends. |
| Export | Definition |
|---|---|
AUTHOR_BADGES | ["certified", "premium"], the display order. |
authorBadges({ certified, premium }) | The badges that apply, in display order. |
createAuthorBadgesReader(entitlements, clock?) | The application's reader: the account's certification, and the grants active now. entitlements is Pick<EntitlementsRepository, "listActiveGrants">. |
AUTHOR_CATALOG_FEATURE | "author_catalog", the provisional flag while the catalog is tested. Granted from the back-office to tester accounts, it only decides whether the app shows the catalog entry points; nothing on the server reads it. |
What the catalog exposes
There is no publication step. A collection is in the catalog when its public link is open (publicEnabled, within its availability window), not archived and not degraded past its owner's ceiling. A deal is in the catalog when it is public. Private deals stay private: they are filtered out before the data leaves the service, so a shared collection holding private deals only is empty, and reads as unknown.
| Export | Definition |
|---|---|
CATALOG_EXPOSED_SHARING | "public". |
isExposedInCatalog(board) | A deal is exposed when it is public, neither trashed nor orphaned. A private deal, or one shared with a sharing group, can stay in a shared collection for its students: it is never returned by the catalog. |
withoutPersonalThemes(board) | The exposed deal without the author's personal themes and inspirations: they never leave with it. |
BoardQuery.exposedInCatalog() is the same rule on the read side: withSharing("public") with the active lifecycle. The catalog also reads it with ownedBy(<author>): only the author's own deals are exposed. A collection can hold another account's public deal (any readable deal can be added to it); the catalog never shows it, nor counts it in dealCount.
An account shows in the catalog when it is active and carries a handle, the address of its author page.
Publication date of a deal
boards_index.published_at is the first time a board became public, written by the board repository on every path that sets sharing (insert, update, batched update, overwrite) and never cleared. The catalog's "recent" order (BoardSort column publishedAt, nulls last) and the subscription feed read it. Migration 0123 backfilled the boards already public with their creation date.
Types
Prop
Type
The public profile is the display name, the avatar once an admin trusted it, the badges and the account's creation date. Never the email; no new profile field (club, presentation) yet.
CatalogService
createCatalogService({ accountsRead, collectionsRead, collectionBoards, boardsRead, overflow, badgesOf }). The reads any signed-in account makes of a showcase. Authorization lives in the service, not in the database: the API builds it on the owner pool and the rules below are the only gate.
| Method | Description |
|---|---|
getAuthor(handle, ctx) | The profile of the active account carrying the handle (normalized), with its badges. Throws NotFoundError(CATALOG_AUTHOR_NOT_FOUND) for an unknown handle or an inactive account. |
listAuthorCollections(handle, ctx) | The author's shared collections, newest first. Archived ones, those degraded past the owner's ceiling and those without an exposed deal are left out; dealCount counts the exposed deals only. |
listAuthorDeals(handle, { page, pageSize }, ctx) | The author's exposed deals, most recently published first, paginated (20 by default, 100 at most). |
listCollectionDeals(collectionId, ctx) | The exposed deals of a shared collection that its owner authored, in the collection's order. Throws NotFoundError(DEAL_COLLECTION_NOT_FOUND) when the public link is closed, the collection is archived, degraded or holds no exposed deal, or its owner is inactive or has no handle. |
HTTP
The CatalogApi of @workspace/api-contract/catalog, served under /catalog to signed-in accounts:
| Route | Method |
|---|---|
GET /catalog/authors/{handle} | getAuthor |
GET /catalog/authors/{handle}/collections | listAuthorCollections |
GET /catalog/authors/{handle}/deals?page&pageSize | listAuthorDeals, { deals, pagination } |
GET /catalog/collections/{id}/deals | listCollectionDeals |
The account DTO carries certified. The back-office toggles it from the accounts table.
Collections
Deal collections, a teacher's private ordered sets of boards with a public link, served as Governed reads and refused for mutation once degraded.
Sharing
The sharing status of a board and the sharing groups through which a teacher shares boards with chosen peers, with the SharingGroupsService.