From 4ab398f0c033cc134ee63bd3191d6ac8faa499ec Mon Sep 17 00:00:00 2001 From: Trezy Date: Sun, 15 Mar 2026 10:33:48 -0500 Subject: [PATCH] docs: add documentation for labelers --- docs/README.md | 2 + docs/guides/labelers.md | 82 ++++++++++++++++++++++++++++++ docs/guides/scripting.md | 65 ++++++++++++++++++++++++ docs/reference/admin-api.md | 99 +++++++++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 docs/guides/labelers.md diff --git a/docs/README.md b/docs/README.md index 8f219b2..62c6576 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,6 +14,7 @@ Building an AppView from scratch means wiring up firehose connections, record st - 🪝 **Index Hooks**: Attach Lua scripts to record collections that fire on every create, update, or delete — sync to search engines, trigger webhooks, or build materialized views in real time - 🌐 **Network Lexicons**: Fetch lexicon schemas directly from the AT Protocol network via DNS authority resolution - ⚡ **Hot Reloading**: Upload or update lexicons at runtime, and new endpoints are available immediately with no restart +- 🏷️ **Label Support**: Subscribe to external [labelers](guides/labelers.md) and surface content labels on records, with self-label detection and color-coded badges in the dashboard - 🛠️ **Admin Dashboard**: Manage lexicons, monitor record stats, and run backfill jobs through a built-in admin API ## Design Principles @@ -32,4 +33,5 @@ Building an AppView from scratch means wiring up firehose connections, record st - [Lexicons](guides/lexicons.md): Upload lexicon schemas and start indexing records - [Lua Scripting](guides/scripting.md): Write custom query and procedure logic - [Index Hooks](guides/index-hooks.md): React to record changes in real time +- [Labelers](guides/labelers.md): Subscribe to external labelers and manage content labels - [Event Logs](guides/event-logs.md): Monitor system activity, debug script errors, and audit admin actions diff --git a/docs/guides/labelers.md b/docs/guides/labelers.md new file mode 100644 index 0000000..6bb09c8 --- /dev/null +++ b/docs/guides/labelers.md @@ -0,0 +1,82 @@ +# Labelers + +Labelers are external services in the AT Protocol network that apply content labels to records. HappyView can subscribe to labelers and store the labels they emit, making them available on records in the admin dashboard and via Lua scripts. + +## How labelers work + +A labeler is identified by its DID and runs as a service on the AT Protocol network. When you subscribe to a labeler, HappyView connects directly to the labeler's WebSocket and streams label events in real time. Each label targets a specific record URI and carries a value like `nudity`, `spam`, or any custom string the labeler defines. + +Labels are stored in a `labels` table in the database. HappyView tracks a cursor per labeler subscription so it can resume from where it left off after a restart. + +Records can also have **self-labels** — labels applied by the record's author and embedded directly in the record's `labels.values` array. These are not managed by external labelers but are displayed alongside external labels in the dashboard. + +## Adding a labeler + +1. Go to **Settings > Labelers** in the dashboard sidebar +2. Click **Add Labeler** +3. Enter the labeler's DID (e.g., `did:plc:ar7c4by46qjdydhdevvrndac`) +4. Click **Add** + +HappyView begins consuming labels from the labeler immediately. The subscription appears in the table with an `active` status. + +You can also add a labeler via the API: + +```sh +curl -X POST http://localhost:3000/admin/labelers \ + -H "$AUTH" \ + -H "Content-Type: application/json" \ + -d '{ "did": "did:plc:ar7c4by46qjdydhdevvrndac" }' +``` + +## Pausing and resuming + +You can pause a labeler subscription to temporarily stop consuming labels without losing your cursor position. Click the pause icon next to the labeler in the table, or use the API: + +```sh +curl -X PATCH http://localhost:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ + -H "$AUTH" \ + -H "Content-Type: application/json" \ + -d '{ "status": "paused" }' +``` + +Resume by clicking the play icon or sending `{ "status": "active" }`. + +## Deleting a labeler + +Deleting a labeler removes the subscription **and all labels it has emitted**. This cannot be undone. + +1. Click the trash icon next to the labeler +2. Confirm in the dialog + +Or via the API: + +```sh +curl -X DELETE http://localhost:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ + -H "$AUTH" +``` + +## Labels on records + +Labels appear in the **Labels** column on the Records page as color-coded badges: + +- **Red** — content warnings: `nudity`, `sexual`, `graphic-media`, `violence`, `gore` +- **Amber** — moderation labels: `spam`, `impersonation` +- **Neutral** — everything else + +Self-labels (applied by the record author) use an outline badge style to distinguish them from external labels. Hover over a badge to see the source labeler's DID. + +Labels are also available in the records API response and in Lua scripts via the `get_labels` and `get_labels_batch` functions. See the [Scripting guide](scripting.md) for details. + +## Permissions + +| Action | Permission | +| ------------------------ | ----------------- | +| View labeler list | `labelers:read` | +| Add or pause/resume | `labelers:create` | +| Delete a labeler | `labelers:delete` | + +## Next steps + +- [Admin API reference](../reference/admin-api.md#labelers) — full endpoint documentation +- [Scripting](scripting.md) — access labels in Lua scripts with `get_labels` and `get_labels_batch` +- [Permissions](permissions.md) — manage user access to labeler operations diff --git a/docs/guides/scripting.md b/docs/guides/scripting.md index 57b891e..88d8425 100644 --- a/docs/guides/scripting.md +++ b/docs/guides/scripting.md @@ -373,6 +373,71 @@ if endpoint then end ``` +### atproto.get_labels + +```lua +local labels = atproto.get_labels(uri) +``` + +Returns an array of labels for a single AT URI. Merges external labels (from subscribed labelers) with self-labels (from the record's `labels.values[]` field). + +| Parameter | Type | Description | +| --------- | ------ | ------------------------------ | +| `uri` | string | AT URI of the record to query | + +Each label in the array is a table with: + +| Field | Type | Description | +| ----- | ------ | ---------------------------------------- | +| `src` | string | DID of the labeler (or record author) | +| `uri` | string | AT URI this label applies to | +| `val` | string | Label value (e.g. "nsfw", "!hide") | +| `cts` | string | Timestamp when the label was created | + +Expired labels are automatically filtered out. Returns an empty array if no labels exist. + +### atproto.get_labels_batch + +```lua +local labels_by_uri = atproto.get_labels_batch(uris) +``` + +Batch version of `get_labels`. Takes an array of AT URIs and returns a table keyed by URI, where each value is an array of labels. + +| Parameter | Type | Description | +| --------- | ----- | ------------------------ | +| `uris` | table | Array of AT URI strings | + +**Returns:** A table keyed by URI. Each value is an array of label tables (same shape as `get_labels`). URIs with no labels have an empty array. + +### Label Examples + +```lua +-- Get labels for a single game +local labels = atproto.get_labels("at://did:plc:abc/games.gamesgamesgamesgames.game/rkey1") +for _, label in ipairs(labels) do + if label.val == "!hide" then + -- skip this game in feed results + end +end + +-- Batch fetch labels for multiple games (efficient for feed hydration) +local uris = {} +for _, item in ipairs(skeleton) do + uris[#uris + 1] = item.game +end + +local labels_by_uri = atproto.get_labels_batch(uris) +for _, uri in ipairs(uris) do + local labels = labels_by_uri[uri] + for _, label in ipairs(labels) do + if label.val == "!hide" then + -- filter out this game + end + end +end +``` + ## JSON API The `json` global provides JSON serialization and deserialization. Available in queries, procedures, and [index hooks](index-hooks.md). diff --git a/docs/reference/admin-api.md b/docs/reference/admin-api.md index 456299d..597df59 100644 --- a/docs/reference/admin-api.md +++ b/docs/reference/admin-api.md @@ -587,6 +587,101 @@ curl -X DELETE http://localhost:3000/admin/users/550e8400-e29b-41d4-a716-4466554 **Response**: `204 No Content` +## Labelers + +Manage external labeler subscriptions. See the [Labelers guide](../guides/labelers.md) for background. + +### Add a labeler + +``` +POST /admin/labelers +``` + +Requires `labelers:create` permission. + +```sh +curl -X POST http://localhost:3000/admin/labelers \ + -H "$AUTH" \ + -H "Content-Type: application/json" \ + -d '{ "did": "did:plc:ar7c4by46qjdydhdevvrndac" }' +``` + +| Field | Type | Required | Description | +| ----- | ------ | -------- | ---------------------- | +| `did` | string | yes | The labeler's AT Protocol DID | + +**Response**: `201 Created` (empty body) + +### List labelers + +``` +GET /admin/labelers +``` + +Requires `labelers:read` permission. + +```sh +curl http://localhost:3000/admin/labelers -H "$AUTH" +``` + +**Response**: `200 OK` + +```json +[ + { + "did": "did:plc:ar7c4by46qjdydhdevvrndac", + "status": "active", + "cursor": 1234, + "created_at": "2026-03-15T00:00:00Z", + "updated_at": "2026-03-15T00:00:00Z" + } +] +``` + +| Field | Type | Description | +| ------------ | ------------ | ------------------------------------------------ | +| `did` | string | The labeler's DID | +| `status` | string | `active` or `paused` | +| `cursor` | number\|null | Last processed event cursor (null if never synced) | +| `created_at` | string | ISO 8601 creation timestamp | +| `updated_at` | string | ISO 8601 last-updated timestamp | + +### Update a labeler + +``` +PATCH /admin/labelers/{did} +``` + +Requires `labelers:create` permission. + +```sh +curl -X PATCH http://localhost:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ + -H "$AUTH" \ + -H "Content-Type: application/json" \ + -d '{ "status": "paused" }' +``` + +| Field | Type | Required | Description | +| -------- | ------ | -------- | ---------------------------- | +| `status` | string | yes | New status: `active` or `paused` | + +**Response**: `200 OK` + +### Delete a labeler + +``` +DELETE /admin/labelers/{did} +``` + +Requires `labelers:delete` permission. Removes the subscription and all labels emitted by this labeler. + +```sh +curl -X DELETE http://localhost:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ + -H "$AUTH" +``` + +**Response**: `204 No Content` + ## Permissions Each admin API endpoint requires a specific permission. See the [Permissions guide](../guides/permissions.md) for the full list of permissions and templates. @@ -617,3 +712,7 @@ Each admin API endpoint requires a specific permission. See the [Permissions gui | `GET /admin/script-variables` | `script-variables:read` | | `POST /admin/script-variables` | `script-variables:create` | | `DELETE /admin/script-variables/{key}`| `script-variables:delete` | +| `POST /admin/labelers` | `labelers:create` | +| `GET /admin/labelers` | `labelers:read` | +| `PATCH /admin/labelers/{did}` | `labelers:create` | +| `DELETE /admin/labelers/{did}` | `labelers:delete` | -- 2.51.2