This is an automated email from the ASF dual-hosted git repository. sergehuber pushed a commit to branch UNOMI-932-unomi3-major-release-story in repository https://gitbox.apache.org/repos/asf/unomi-site.git
commit 45d33ccef93f5c1c48e66a7531a8209f53a07673 Author: Serge Huber <[email protected]> AuthorDate: Thu Jul 16 12:10:59 2026 +0200 Fix Unomi 3.1 site examples against live API behavior (UNOMI-932) Document API key regen for plainTextKey, require scopes before context/events, and put view pageInfo under target.properties so built-in referrer rules stay quiet. --- src/main/webapp/blog/unomi-3-request-tracing.html | 26 ++++++++++- src/main/webapp/blog/unomi-3-shell-commands.html | 19 ++++++-- src/main/webapp/get-started.html | 25 ++++++++-- src/main/webapp/llms-full.txt | 11 ++++- src/main/webapp/llms.txt | 3 +- src/main/webapp/tutorial.html | 56 +++++++++++++---------- 6 files changed, 103 insertions(+), 37 deletions(-) diff --git a/src/main/webapp/blog/unomi-3-request-tracing.html b/src/main/webapp/blog/unomi-3-request-tracing.html index f5bfdf8..faf1858 100644 --- a/src/main/webapp/blog/unomi-3-request-tracing.html +++ b/src/main/webapp/blog/unomi-3-request-tracing.html @@ -51,7 +51,21 @@ layout: default <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>{"errorMessage":"Insufficient privileges to access tracing information"}</code></pre> <p>Integration coverage: <code>EventsCollectorIT#testEventsCollectorWithExplain</code> and <code>#testEventsCollectorWithExplainUnauthorized</code>.</p> + <h2 class="h4 mt-5 mb-3">Prerequisite: create the scope</h2> + <p>JSON Schema validation rejects events whose <code>scope</code> is unknown for the tenant + (<code>Unknown scope value … for value myscope</code>). Create the scope first:</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -X POST http://localhost:8181/cxs/scopes \ + -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \ + -H 'Content-Type: application/json' \ + -d '{ + "itemId": "myscope", + "itemType": "scope", + "metadata": { "id": "myscope", "name": "My scope" } + }'</code></pre> + <h2 class="h4 mt-5 mb-3">Example: event collector</h2> + <p class="small text-muted">Include <code>target.properties.pageInfo</code> (at least <code>referringURL</code> / <code>pagePath</code>). + Built-in rules such as <code>sessionReferrer</code> read those fields; omitting them still works but logs noisy WARNs.</p> <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -X POST 'http://localhost:8181/cxs/eventcollector?explain=true' \ -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \ -H 'Content-Type: application/json' \ @@ -70,7 +84,17 @@ layout: default "target": { "itemType": "page", "scope": "myscope", - "itemId": "homepage" + "itemId": "homepage", + "properties": { + "pageInfo": { + "pageID": "homepage", + "pagePath": "/home", + "pageName": "Home", + "destinationURL": "https://example.com/home", + "referringURL": "https://example.com/", + "language": "en" + } + } } } ] diff --git a/src/main/webapp/blog/unomi-3-shell-commands.html b/src/main/webapp/blog/unomi-3-shell-commands.html index 148e6ed..83497da 100644 --- a/src/main/webapp/blog/unomi-3-shell-commands.html +++ b/src/main/webapp/blog/unomi-3-shell-commands.html @@ -56,8 +56,11 @@ unomi:version unomi:cache --stats</code></pre> <h2 class="h4 mt-5 mb-3">Tenant context first</h2> - <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Create a tenant (generates public + private API keys) + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Create a tenant (hashed keys are stored; plaintext only via create apikey) unomi:crud create tenant '{"itemId":"acme","name":"Acme Corp"}' +unomi:crud create apikey '{"tenantId":"acme","keyType":"PUBLIC"}' +unomi:crud create apikey '{"tenantId":"acme","keyType":"PRIVATE"}' +# plaintext key is printed once for each create apikey — store it immediately unomi:crud list tenant unomi:tenant-set acme @@ -115,6 +118,13 @@ unomi:crud update profile demo-ada '{"firstName":"Ada","lastName":"Lovelace","em unomi:crud delete profile demo-ada</code></pre> <p class="small text-muted">There are no separate <code>profile-list</code> / <code>profile-view</code> commands in Unomi 3 — use <code>unomi:crud list|read|delete profile</code>.</p> + <h3 class="h6 mt-4 mb-2">Scope first</h3> + <p class="small text-muted">Sessions and events must reference a scope that already exists for the tenant. + Without it, event schema validation logs <code>Unknown scope value</code> and rejects the payload.</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Scope name lives under metadata (not a top-level "name" field) +unomi:crud create scope '{"itemId":"myscope","metadata":{"id":"myscope","name":"My scope"}}' +unomi:crud list scope</code></pre> + <h3 class="h6 mt-4 mb-2">Sessions</h3> <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:crud help session unomi:crud list session -n 20 @@ -134,11 +144,10 @@ unomi:crud delete session demo-session-1</code></pre> unomi:crud list event -n 50 unomi:crud read event EVENT_ID -# Send / persist a view event (create goes through EventService.send) -unomi:crud create event '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"homepage"},"properties":{"pageInfo":{"pagePath":"/home"}},"persistent":true}' +# View events: put pageInfo under target.properties (built-in sessionReferrer reads referringURL there) +unomi:crud create event '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"homepage","properties":{"pageInfo":{"pagePath":"/home","pageName":"Home","destinationURL":"https://example.com/home","referringURL":"https://example.com/","language":"en"}}},"persistent":true}' -# Re-send / update the same event id with new properties -unomi:crud update event demo-event-1 '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"checkout"},"properties":{"pageInfo":{"pagePath":"/checkout"}},"persistent":true}' +unomi:crud update event demo-event-1 '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"checkout","properties":{"pageInfo":{"pagePath":"/checkout","pageName":"Checkout","destinationURL":"https://example.com/checkout","referringURL":"https://example.com/home","language":"en"}}},"persistent":true}' unomi:crud delete event demo-event-1 diff --git a/src/main/webapp/get-started.html b/src/main/webapp/get-started.html index 4c2ef2b..ede669f 100644 --- a/src/main/webapp/get-started.html +++ b/src/main/webapp/get-started.html @@ -32,7 +32,7 @@ structured_data: > "@type": "HowToStep", "position": 3, "name": "Create a tenant", - "text": "POST /cxs/tenants as karaf/karaf and save the public and private API keys from the response." + "text": "POST /cxs/tenants as karaf/karaf, then POST /cxs/tenants/{id}/apikeys?type=PUBLIC and type=PRIVATE and save each plainTextKey." }, { "@type": "HowToStep", @@ -229,7 +229,7 @@ volumes: <div class="step-number me-3 flex-shrink-0">3</div> <div class="flex-grow-1"> <h3 class="h6 fw-semibold mb-2">Create a tenant (required in 3.1)</h3> - <p class="small text-muted">System admin credentials create tenants. Save both API keys from the JSON response.</p> + <p class="small text-muted">System admin credentials create tenants. Creation stores hashed keys only (<code>maskedKey</code> in the response) — regenerate each key once and save <code>plainTextKey</code> immediately.</p> <pre><code>curl -X POST http://localhost:8181/cxs/tenants \ --user karaf:karaf \ -H "Content-Type: application/json" \ @@ -239,10 +239,16 @@ volumes: "name": "Default Tenant", "description": "Default tenant for quick start" } - }'</code></pre> + }' + +# One-time plaintext secrets (replaces any existing key of that type) +curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PUBLIC' \ + --user karaf:karaf +curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PRIVATE' \ + --user karaf:karaf</code></pre> <ul class="small text-muted mt-2 mb-0"> - <li><strong>PUBLIC</strong> key — use as <code>X-Unomi-Api-Key</code> on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code></li> - <li><strong>PRIVATE</strong> key — basic auth <code>default:<private-key></code> for admin APIs (scopes, rules, schemas, …)</li> + <li><strong>PUBLIC</strong> <code>plainTextKey</code> — use as <code>X-Unomi-Api-Key</code> on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code></li> + <li><strong>PRIVATE</strong> <code>plainTextKey</code> — basic auth <code>default:<private-key></code> for admin APIs (scopes, rules, schemas, …)</li> </ul> </div> </div> @@ -254,6 +260,15 @@ volumes: <h3 class="h6 fw-semibold mb-2">Verify cluster & request context</h3> <p class="small text-muted mb-2">Cluster (system admin):</p> <pre><code>curl -k https://localhost:9443/cxs/cluster -u karaf:karaf</code></pre> + <p class="small text-muted mt-3 mb-2">Create a scope before context or events (schema validation rejects unknown scopes):</p> + <pre><code>curl -X POST http://localhost:8181/cxs/scopes \ + -u "default:YOUR_PRIVATE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "itemId": "default", + "itemType": "scope", + "metadata": { "id": "default", "name": "Default scope" } + }'</code></pre> <p class="small text-muted mt-3 mb-2">First context request (public API key required):</p> <pre><code>curl -X POST "http://localhost:8181/cxs/context.json?sessionId=1234" \ -H "Content-Type: application/json" \ diff --git a/src/main/webapp/llms-full.txt b/src/main/webapp/llms-full.txt index eae4cc2..6b1ae4d 100644 --- a/src/main/webapp/llms-full.txt +++ b/src/main/webapp/llms-full.txt @@ -193,7 +193,7 @@ services: docker compose up ``` -Create a tenant, then call context with the public API key: +Create a tenant, regenerate API keys for plaintext secrets, create a scope, then call context with the public API key: ``` curl -X POST http://localhost:8181/cxs/tenants \ @@ -201,6 +201,15 @@ curl -X POST http://localhost:8181/cxs/tenants \ -H "Content-Type: application/json" \ -d '{"requestedId":"default","properties":{"name":"Default Tenant"}}' +curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PUBLIC' --user karaf:karaf +curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PRIVATE' --user karaf:karaf +# Save plainTextKey from each response (tenant create only returns maskedKey) + +curl -X POST http://localhost:8181/cxs/scopes \ + -u 'default:YOUR_PRIVATE_API_KEY' \ + -H "Content-Type: application/json" \ + -d '{"itemId":"default","itemType":"scope","metadata":{"id":"default","name":"Default scope"}}' + curl -X POST "http://localhost:8181/cxs/context.json?sessionId=1234" \ -H "Content-Type: application/json" \ -H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \ diff --git a/src/main/webapp/llms.txt b/src/main/webapp/llms.txt index 3979623..8525ea2 100644 --- a/src/main/webapp/llms.txt +++ b/src/main/webapp/llms.txt @@ -69,10 +69,11 @@ The fastest way to try Apache Unomi 3.1 is with Docker Compose (Elasticsearch 9. docker compose up ``` -Then create a tenant (`POST /cxs/tenants` as `karaf:karaf`) and save the returned API keys: +Then create a tenant (`POST /cxs/tenants` as `karaf:karaf`), regenerate keys via `POST /cxs/tenants/{id}/apikeys?type=PUBLIC|PRIVATE` (save each `plainTextKey` — create only returns `maskedKey`), and create a scope before context/events: - **Public key** → header `X-Unomi-Api-Key` for `/cxs/context.json` and `/cxs/eventcollector` - **Private key** → basic auth `tenantId:privateApiKey` for admin APIs (scopes, rules, schemas, searches) +- **Scope** → `POST /cxs/scopes` before any context or event that references that scope id System admin (`karaf:karaf`) is for tenant management and cluster operations, not for public context/event calls in native 3.1 mode. diff --git a/src/main/webapp/tutorial.html b/src/main/webapp/tutorial.html index 5219309..ce34cd3 100644 --- a/src/main/webapp/tutorial.html +++ b/src/main/webapp/tutorial.html @@ -22,7 +22,7 @@ structured_data: > "@type": "HowToStep", "position": 2, "name": "Create a tenant", - "text": "POST /cxs/tenants as karaf/karaf and save the public and private API keys." + "text": "POST /cxs/tenants as karaf/karaf, then regenerate PUBLIC and PRIVATE keys via /cxs/tenants/{id}/apikeys and save each plainTextKey." }, { "@type": "HowToStep", @@ -145,7 +145,7 @@ services: <div class="col-lg-10"> <span class="badge badge-primary mb-3">Step 2 · Unomi 3.1</span> <h2>Create a Tenant</h2> - <p class="text-muted">In Unomi 3.1, data lives under a <strong>tenant</strong>. Create one with the system admin user, then use the returned API keys for everything else.</p> + <p class="text-muted">In Unomi 3.1, data lives under a <strong>tenant</strong>. Create one with the system admin user, then regenerate API keys to obtain the one-time plaintext values.</p> <pre><code>curl -X POST http://localhost:8181/cxs/tenants \ --user karaf:karaf \ -H "Content-Type: application/json" \ @@ -155,8 +155,14 @@ services: "name": "Default Tenant", "description": "Tutorial tenant" } - }'</code></pre> - <p class="small text-muted mt-3">From the response, copy:</p> + }' + +# Tenant create only returns maskedKey. Regenerate to get plainTextKey (shown once): +curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PUBLIC' \ + --user karaf:karaf +curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PRIVATE' \ + --user karaf:karaf</code></pre> + <p class="small text-muted mt-3">From each apikeys response, copy <code>plainTextKey</code>:</p> <ul class="small"> <li><code>YOUR_PUBLIC_API_KEY</code> — header <code>X-Unomi-Api-Key</code> for <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code></li> <li><code>YOUR_PRIVATE_API_KEY</code> — basic auth <code>default:YOUR_PRIVATE_API_KEY</code> for scopes, rules, schemas, searches</li> @@ -200,7 +206,7 @@ karaf@root()> unomi:start</code></pre> <p>Create a tenant as in <a href="#create-tenant">Step 2</a>, then call context with <code>X-Unomi-Api-Key</code>. Cluster check: <code>https://localhost:9443/cxs/cluster</code> (<code>karaf</code>/<code>karaf</code>).</p> <div class="alert alert-info small" role="alert"> - <i class="bi bi-terminal me-1"></i> <strong>Tip:</strong> SSH console <code>ssh -p 8102 karaf@localhost</code> — <code>profile-list</code>, <code>event-tail</code>, <code>rule-list</code>. Full detail: <a href="https://unomi.apache.org/manual/latest/index.html#_quick_start">manual quick start</a>. + <i class="bi bi-terminal me-1"></i> <strong>Tip:</strong> SSH console <code>ssh -p 8102 karaf@localhost</code> — <code>unomi:crud list profile</code>, <code>unomi:event-tail</code>, <code>unomi:crud list rule</code>. Full detail: <a href="/blog/unomi-3-shell-commands.html">shell commands post</a> · <a href="https://unomi.apache.org/manual/latest/index.html#_quick_start">manual quick start</a>. </div> </div> </div> @@ -220,6 +226,22 @@ karaf@root()> unomi:start</code></pre> <h2>Your First API Calls</h2> <p class="text-muted">With Unomi 3.1 running and a tenant created, explore the REST API. Replace <code>YOUR_PUBLIC_API_KEY</code> / <code>YOUR_PRIVATE_API_KEY</code> with values from <a href="#create-tenant">Step 2</a>.</p> + <!-- Create a scope first --> + <h3 class="h6 fw-semibold mt-4 mb-3">Create a scope</h3> + <p>Context requests and events must use a <strong>scope</strong> that already exists for the tenant. + Schema validation rejects unknown scopes (<code>Unknown scope value</code> in the logs):</p> +<pre><code>curl -X POST http://localhost:8181/cxs/scopes \ + -u "default:YOUR_PRIVATE_API_KEY" \ + -H "Content-Type: application/json" \ + --data-raw '{ + "itemId": "my-website", + "itemType": "scope", + "metadata": { + "id": "my-website", + "name": "My Website Scope" + } + }'</code></pre> + <!-- Read context --> <h3 class="h6 fw-semibold mt-4 mb-3">Read the current context</h3> <p>The <code>/cxs/context.json</code> endpoint is the primary public endpoint. In 3.1 it requires a <strong>public API key</strong>:</p> @@ -230,7 +252,7 @@ karaf@root()> unomi:start</code></pre> "source": { "itemId": "homepage", "itemType": "page", - "scope": "default" + "scope": "my-website" }, "requiredProfileProperties": ["*"], "requiredSessionProperties": ["*"], @@ -238,21 +260,6 @@ karaf@root()> unomi:start</code></pre> "requireScores": true }'</code></pre> - <!-- Create a scope --> - <h3 class="h6 fw-semibold mt-4 mb-3">Create a scope</h3> - <p>Events in Unomi must be associated with a <strong>scope</strong>. Create one before sending events:</p> -<pre><code>curl -X POST http://localhost:8181/cxs/scopes \ - -u "default:YOUR_PRIVATE_API_KEY" \ - -H "Content-Type: application/json" \ - --data-raw '{ - "itemId": "my-website", - "itemType": "scope", - "metadata": { - "id": "my-website", - "name": "My Website Scope" - } - }'</code></pre> - <!-- Send a custom event --> <h3 class="h6 fw-semibold mt-4 mb-3">Send a custom event</h3> <p>Before sending a custom event, register a <strong>JSON Schema</strong> to validate it (required since Unomi 2.0):</p> @@ -341,7 +348,7 @@ karaf@root()> unomi:start</code></pre> -u "default:YOUR_PRIVATE_API_KEY"</code></pre> <div class="alert alert-info small" role="alert"> - <i class="bi bi-terminal me-1"></i> <strong>SSH Console shortcuts:</strong> <code>event-tail</code> watches events in real time, <code>event-list</code> shows recent events, <code>profile-list</code> lists recently modified profiles, <code>profile-view PROFILE_UUID</code> shows profile details. + <i class="bi bi-terminal me-1"></i> <strong>SSH Console shortcuts:</strong> <code>unomi:event-tail</code> watches events in real time, <code>unomi:crud list event</code> / <code>unomi:crud list profile</code> list recent items, <code>unomi:crud read profile PROFILE_UUID</code> shows profile details. </div> </div> </div> @@ -383,6 +390,7 @@ karaf@root()> unomi:start</code></pre> "pageName": document.title, "pagePath": document.location.pathname, "destinationURL": document.location.origin + document.location.pathname, + "referringURL": document.referrer || document.location.origin + "/", "language": "en", "categories": [], "tags": [] @@ -489,7 +497,7 @@ const unomiWebTracker = useTracker(); } }] }'</code></pre> - <p class="small text-muted">After creating this rule, reload your tracked page a few times, then check the profile with <code>profile-view PROFILE_UUID</code> in the SSH console to see the counter increase.</p> + <p class="small text-muted">After creating this rule, reload your tracked page a few times, then check the profile with <code>unomi:crud read profile PROFILE_UUID</code> in the SSH console to see the counter increase.</p> <!-- Copy event properties to profile --> <h3 class="h6 fw-semibold mt-4 mb-3">Map event data to a profile</h3> @@ -667,7 +675,7 @@ karaf@root()> log:set DEBUG org.apache.unomi.schema.impl.SchemaServiceImpl # Watch logs in real-time karaf@root()> log:tail</code></pre> - <p class="small text-muted mb-0">Useful commands: <code>event-tail</code>, <code>event-list</code>, <code>rule-list</code>, <code>rule-tail</code>, <code>profile-list</code>, <code>profile-view <ID></code></p> + <p class="small text-muted mb-0">Useful commands: <code>unomi:event-tail</code>, <code>unomi:rule-tail</code>, <code>unomi:crud list event|rule|profile</code>, <code>unomi:crud read profile <ID></code></p> </div> </div> </div>
