Joomla connector
A Joomla 4.x/5.x system plugin (plg_system_bainquet) that maps com_content articles and categories to bAInquet IngestItems, signs every request with the connector HMAC scheme, and POSTs idempotent batches.
The plugin lives under connectors/joomla/plg_system_bainquet and sends X-Connector-Type: joomla.
What it maps
| Joomla content | IngestItem type | Stable id | Notes |
|---|---|---|---|
com_content article | post | post:<articleId> | The SourceType enum has no article value, so articles map to the in-enum post (the strict server schema would reject article). introtext + fulltext become html plus a deterministic text rendition. metadesc and metakey map into json.seo. |
| Category | category | category:<categoryId> | Description becomes html. |
Language is the item's language tag (or the configured default when *), normalized to BCP-47. updated_at is RFC3339 UTC with a Z suffix.
Install
Install the plugin package via System, Install, Extensions, then enable System - bAInquet Connector under System, Manage, Plugins.
Configure
Open the plugin (System, Plugins, System - bAInquet Connector).
- API base URL defaults to
https://api.bainquet.online/v1/ingest. - Connector token: the
connectorId.secrettoken, shown once in the dashboard. It is stored in the plugin params (#__extensions), never in a web-served file or the action log. - Website ID: the website id the token is scoped to.
- Site domain: sent as
X-Site-Domain(defaults to this site host; must match item URLs). - Default language: a BCP-47 fallback for content with no explicit language.
Then run the backfill (php cli/bainquet_sync.php, wired as a Joomla console command and a plg_task_bainquet Scheduler task in a packaged build).
How incremental sync works
The system plugin listens to Joomla content events.
- Upsert.
onContentAfterSavemaps the article and POSTs toPOST /v1/ingest/item. - Tombstone.
onContentAfterDeleteandonContentChangeStatePOST toPOST /v1/ingest/delete. Unpublishing, archiving, or trashing an article tombstones it, so the public node drops it.
How backfill works
php cli/bainquet_sync.phpIt walks published, public-access articles and categories in chunks of 100 per batch and batch-POSTs to POST /v1/ingest/batch. The access and published predicate is applied at the SQL level (WHERE state = 1 AND access IN (public view levels)), so restricted or unpublished content is never loaded.
php cli/bainquet_sync.php --dry-runThe dry run builds and prints the batch it would send, without posting.
Out of scope
- Single-item upserts are sent synchronously on the content event. The
#__bainquet_outboxtable plus Scheduler drain with debounce is a later-phase enhancement. Planned - Menus, contacts, tags, custom fields, and VirtueMart products are future work; this connector ships article and category mapping plus full transport and glue. Planned
HMAC signing
Every request is signed exactly as the server verifies it, using the shared bq.connector.hmac.v1 scheme:
signingKey = HKDF-SHA256(secret, salt = websiteId, info = "bq.connector.hmac.v1", 32 bytes)
canonical = METHOD \n path \n sha256(body) \n timestamp \n nonce \n websiteId
X-Signature = hex HMAC-SHA256(signingKey, canonical)The exact serialized bytes are signed and sent (Joomla Http::post($url, $rawString, ...) sends the string verbatim, with no re-encoding). Headers: Authorization: Bearer <token>, X-Site-Domain, X-Connector-Type: joomla, X-Connector-Version, X-Signature, X-Timestamp, X-Nonce, X-Body-Sha256, and Idempotency-Key. Retries on 5xx and 429 reuse the same Idempotency-Key with a fresh nonce and timestamp.
The signing is parity-tested with standalone tests that need no Joomla boot (php tests/SignerParityTest.php and php tests/MapperTest.php); the signer output matches the project-wide known-good vector byte-for-byte. Full content-event, CLI, and database behavior still requires a live Joomla install to validate. The full scheme is documented in Ingestion and signing.