Skip to content

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 contentIngestItem typeStable idNotes
com_content articlepostpost:<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.
Categorycategorycategory:<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).

  1. API base URL defaults to https://api.bainquet.online/v1/ingest.
  2. Connector token: the connectorId.secret token, shown once in the dashboard. It is stored in the plugin params (#__extensions), never in a web-served file or the action log.
  3. Website ID: the website id the token is scoped to.
  4. Site domain: sent as X-Site-Domain (defaults to this site host; must match item URLs).
  5. 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. onContentAfterSave maps the article and POSTs to POST /v1/ingest/item.
  • Tombstone. onContentAfterDelete and onContentChangeState POST to POST /v1/ingest/delete. Unpublishing, archiving, or trashing an article tombstones it, so the public node drops it.

How backfill works

bash
php cli/bainquet_sync.php

It 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.

bash
php cli/bainquet_sync.php --dry-run

The 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_outbox table 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.

Owner-controlled structured data for AI.