pipeline-product-and-build-ingest-api

Pipeline-to-store product and build ingest API

The store is a WooCommerce site, but its product pages and build downloads are not maintained by hand — a CI packaging pipeline pushes them in. Two REST endpoints on the store, both POST under the youteacher/v1 namespace, are that ingest surface: one to record where a freshly built binary lives, one to create or refresh the whole product listing from the game's own manifest. Together they let a build run end with the store already updated, no human step in between.

The slug is the whole mapping. The pipeline knows only two things: which game it is building (the game's repository name) and which platform it just produced. It does not know or carry any WooCommerce product id. The convention that ties the two systems together is that the product slug equals the game slug — the store owns that mapping, and both endpoints resolve a product simply by looking one up by path under the product post type. This keeps the pipeline stateless about the store: it names the game, and the store finds the row.

/set-build records one platform's build location. Its body carries the game, a platform, and an object key pointing at the built artifact in a private object store. Three platforms are accepted: two desktop builds meant to be downloaded, and a web build meant to be played in the browser. The endpoint validates the platform against that fixed set, finds the product by slug (returning a 404 if the game has no product yet), and writes the key into a per-platform meta field on that product. The download and play routes read those same per-platform fields later, so this one write is what makes a build reachable to buyers.

/upsert-product builds the listing from the game's manifest. Its body is the store block of the game's game.json — tagline, price, category, who it's for, how it plays, feature cards, what's included, setup steps — plus an optional image list. The handler is idempotent by slug: if a product with that slug exists it is updated, otherwise a new virtual simple product is created with that slug, and nothing is ever deleted. It sets name, price, short description and category, and renders the structured manifest fields into a styled description HTML block. Every field is optional and guarded, and the entire handler is wrapped so that a malformed payload returns a JSON error instead of a fatal — a bad manifest can never take the endpoint down.

The image gallery is optional, failure-tolerant, and deduplicated by content. Images arrive base64-encoded in the same payload. Before doing any work the handler computes a content fingerprint over the incoming images; if it matches the fingerprint stored from the last successful sideload, the gallery step is skipped entirely — re-running the pipeline with unchanged art produces no duplicate media. When the images have changed, each one is decoded and sideloaded into the media library independently: a single bad image is skipped and noted in a warnings list rather than aborting the batch. The first successful image becomes the featured image and the rest the gallery. If every image fails, the existing gallery is left untouched rather than being wiped — a failed upload never degrades a listing that already looked right.

Both endpoints are authenticated by a shared secret. Each carries a permission check that compares a secret supplied in a request header against a value held only in the environment, using a constant-time comparison, and refuses the call unless a secret is actually configured. This is a machine-to-machine surface — the pipeline holds the secret, no browser or buyer ever touches these routes — kept deliberately narrow: create-or-update only, never destructive.

Related

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →