Bridge Training

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.

BadgeWhen
certifiedaccounts.certified, set by hand from the back-office (AccountsService.setCertified). Shown in blue.
premiumA 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.
ExportDefinition
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.

ExportDefinition
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

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.

MethodDescription
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:

RouteMethod
GET /catalog/authors/{handle}getAuthor
GET /catalog/authors/{handle}/collectionslistAuthorCollections
GET /catalog/authors/{handle}/deals?page&pageSizelistAuthorDeals, { deals, pagination }
GET /catalog/collections/{id}/dealslistCollectionDeals

The account DTO carries certified. The back-office toggles it from the accounts table.